Hi,
Currently setting up a testing framework using Cypress for our NextJS application. We handle authentication using the nextjs-auth0 library as recommended.
Using this library poses a problem authenticating when under test as we need to exercise the protected routes via logging in our test account etc. For obvious reasons, I do not want to login to the application via the Auth0 UI which is triggered when using the /api/auth/login, callback etc etc, although this does work as expected.
There doesn’t seem to be a great deal of information on this. I have managed to retrieve the accessToken for the user through the <domain>/oauth/token
endpoint but I am struggling to understand how I go about creating a session, encrypting this, and persisting it as the appSession.0
cookie.
I found this library cypress-nextjs-auth0 which claims to do what I need but when trying to use it I get errors due to it being unmaintained and last updated a few years ago. I have repurposed this library to hopefully get this functionality to work. The encryption algorithm is as follow:
import hkdf from 'futoin-hkdf';
import * as jose from 'jose';
const BYTE_LENGTH = 32;
const ENCRYPTION_INFO = 'JWE CEK';
const options = { hash: 'SHA-256' };
const deriveKey = (secret: any) => {
return hkdf(secret, BYTE_LENGTH, { info: ENCRYPTION_INFO, ...options });
};
const encrypt = async (arg: any) => {
const { secret, ...thingToEncrypt } = arg;
const key = jose.JWK.asKey(deriveKey(secret));
const epochNow = (Date.now() / 1000) | 0;
return Promise.resolve(
jose.JWE.encrypt(JSON.stringify(thingToEncrypt), key, {
alg: 'dir',
enc: 'A256GCM',
uat: epochNow,
iat: epochNow,
exp: epochNow + 7 * 24 * 60 * 60,
}),
);
};
export default encrypt;
I have managed to fetch the user from /oauth/token endpoint, and encrypt a payload to store to the cookie appSession
, but when I then attempt to call /api/auth/me
to confirm the user is authenticated I get back a 401.
This is the main login command:
let cachedUsername: string;
Cypress.Commands.add('login', (credentials = {}) => {
const { username, password } = credentials;
const cachedUserIsCurrentUser = cachedUsername && cachedUsername === username;
const _credentials = {
username: username || Cypress.env('auth0Username'),
password: password || Cypress.env('auth0Password'),
};
const sessionCookieName = Cypress.env('auth0SessionCookieName');
try {
cy.getCookie(sessionCookieName).then(cookieValue => {
/* Skip logging in again if session already exists */
if (cookieValue && cachedUserIsCurrentUser) {
return true;
} else {
cy.clearCookies();
cy.getUserTokens(_credentials).then((response: any) => {
const { access_token, expires_in, id_token, scope } = response.body;
cy.getUserInfo(access_token).then((user: any) => {
const payload = {
secret: Cypress.env('auth0CookieSecret'),
user,
idToken: id_token,
accessToken: access_token,
accessTokenScope: scope,
accessTokenExpiresAt: Date.now() + expires_in,
createdAt: Date.now(),
};
cy.task('encrypt', payload).then((encryptedSession: any) => {
cy._setAuth0Cookie(encryptedSession);
});
});
});
}
});
} catch (error) {
// throw new Error(error);
}
});
And finally the setting of the cookie:
import { serialize } from 'cookie';
const MAX_COOKIE_SIZE = 4096;
const COOKIE_OPTIONS = {
domain: Cypress.env('auth0CookieDomain'),
path: Cypress.env('auth0CookiePath'),
httpOnly: Cypress.env('auth0CookieHttpOnly'),
sameSite: Cypress.env('auth0CookieCookieSameSite'),
secure: Cypress.env('auth0CookieCookieSecure'),
transient: Cypress.env('auth0CookieTransient'),
};
const SESSION_COOKIE_NAME = Cypress.env('auth0SessionCookieName');
Cypress.Commands.add('_setAuth0Cookie', encryptedSession => {
const emptyCookie = serialize(`${SESSION_COOKIE_NAME}.0`, '', COOKIE_OPTIONS);
const chunkSize = MAX_COOKIE_SIZE - emptyCookie.length;
const value = encryptedSession;
const chunkCount = Math.ceil(value.length / chunkSize);
if (chunkCount > 1) {
for (let i = 0; i < chunkCount; i++) {
const chunkValue = value.slice(i * chunkSize, (i + 1) * chunkSize);
const chunkCookieName = `${SESSION_COOKIE_NAME}.${i}`;
cy.setCookie(chunkCookieName, chunkValue, COOKIE_OPTIONS);
}
/**
* Delete the cookie that is not splitted to ensure that auth0 uses only
* the splitted ones.
*/
cy._clearAuth0Cookie();
} else {
cy._clearAuth0SplittedCookies().then(() => {
/**
* Set one main cookie because its value does not exceed the max size,
* and nextjs-auth0 needs at least one cookie to be set.
*/
cy.setCookie(SESSION_COOKIE_NAME, value, COOKIE_OPTIONS);
});
}
});
I have been stuck on this for a few days now and there doesn’t seem to be much support online for testing using nextjs-auth0 without hitting the endpoints.