The following worked for me, and I hope it will be helpful to someone else as well:
-
Setup a route which is publicly available (this is key for this to work) and void of both functionality and content (if you already have a public route, you can use this).
/** Dummy component required for E2E testing */
@Component({ selector: 'app-public', template: `<!-- Test component -->` })
export class PublicComponent {}
/** My app module **/
@NgModule({
declarations: [AppComponent, PublicComponent],
imports: [
RouterModule.forRoot(
[
...
{ path: 'hidden', component: PublicComponent }, // This route is not in use by anything other than E2E testing
],
),
....
})
export class AppModule {}
-
Create the custom login command:
Cypress.Commands.add('login', (email, password) => {
Cypress.log({ name: 'login via Auth0' });
// Direct browser to only route in my application which is not bound by an AuthGuard.
// The reason for doing this is so that we can access localStorage/cookies on the correct domain
cy.visit('/hidden');
const client_id = Cypress.env.CLIENT_ID;
const secret = Cypress.env.CLIENT_SECRET;
const audience = Cypress.env.AUDIENCE; // https://${tenantDomain}/api/v2/.
const rscope = 'openid profile email offline_access';
return cy
.request({
url: Cypress.env.AUTH_URL, // https://${tenantDomain}/oauth/token
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: {
grant_type: 'password',
username: email,
password: password,
audience: audience,
scope: rscope,
client_id: client_id,
client_secret: secret,
},
})
.then(response => {
const { scope, access_token, id_token, token_type, expires_in, refresh_token } = response.body;
cy.window().then(win => {
const payload = JSON.parse(Buffer.from(id_token.split('.')[1], 'base64').toString('ascii'));
win.localStorage.setItem(
`@@auth0spajs@@::${client_id}::${audience}::${rscope}`,
JSON.stringify({
body: {
client_id,
access_token,
id_token,
scope,
expires_in,
token_type,
refresh_token,
decodedToken: {
claims: Object.assign({}, payload, { __raw: id_token }),
encoded: {
header: id_token.split('.')[0],
payload: id_token.split('.')[1],
signature: id_token.split('.')[2],
},
header: JSON.parse(Buffer.from(id_token.split('.')[0], 'base64').toString('ascii')),
user: {
email: payload.email,
email_verified: payload.email_verified,
name: payload.name,
nickname: payload.nickname,
picture: payload.picture,
sub: payload.sub,
updated_at: payload.updated_at,
},
},
audience,
},
expiresAt: Math.floor(Date.now() / 1000) + expires_in,
})
);
});
cy.setCookie('auth0.is.authenticated', 'true');
});
});
-
Now create a test:
it('should show home page', () => {
cy.login(Cypress.env.USER, Cypress.env.PASSWORD).then(() => {
cy.visit('/');
cy.url().should('contain', '/home/check-in');
});
});
The client_id and client_secret must match what the application is configured for. I could not make this work using a new Auth0 Application for this (which other tutorials specify). The reason for this, is that the auth-spa-js checks localStorage by key, and the key contains the client_id. If you use a different client_id for your tests, the application will not pick this up.
You also have to add “Password” grant in Auth0 → Applications → {your application} → Advanced (which Auth0 advices against, but as previously stated I cannot setup a dedicated Auth0 application for this)