@auth0/nextjs-auth0
Version:
Auth0 Next.js SDK
114 lines (113 loc) • 5.67 kB
JavaScript
import * as cookies from "../cookies.js";
import { AbstractSessionStore } from "./abstract-session-store.js";
import { LEGACY_COOKIE_NAME, normalizeStatelessSession } from "./normalize-session.js";
export class StatelessSessionStore extends AbstractSessionStore {
constructor({ secret, rolling, absoluteDuration, inactivityDuration, cookieOptions }) {
super({
secret,
rolling,
absoluteDuration,
inactivityDuration,
cookieOptions
});
this.connectionTokenSetsCookieName = "__FC";
}
async get(reqCookies) {
const cookieValue = cookies.getChunkedCookie(this.sessionCookieName, reqCookies) ??
cookies.getChunkedCookie(LEGACY_COOKIE_NAME, reqCookies, true);
if (!cookieValue) {
return null;
}
const originalSession = await cookies.decrypt(cookieValue, this.secret);
if (!originalSession) {
return null;
}
const normalizedStatelessSession = normalizeStatelessSession(originalSession);
// As connection access tokens are stored in seperate cookies,
// we need to get all cookies and only use those that are prefixed with `this.connectionTokenSetsCookieName`
const connectionTokenSetsCookies = this.getConnectionTokenSetsCookies(reqCookies);
const connectionTokenSets = [];
for (const cookie of connectionTokenSetsCookies) {
const decryptedCookie = await cookies.decrypt(cookie.value, this.secret);
if (decryptedCookie) {
connectionTokenSets.push(decryptedCookie.payload);
}
}
return {
...normalizedStatelessSession,
// Ensure that when there are no connection token sets, we omit the property.
...(connectionTokenSets.length
? {
connectionTokenSets
}
: {})
};
}
/**
* save adds the encrypted session cookie as a `Set-Cookie` header.
*/
async set(reqCookies, resCookies, session) {
const { connectionTokenSets, ...originalSession } = session;
const maxAge = this.calculateMaxAge(session.internal.createdAt);
const expiration = Math.floor(Date.now() / 1000) + maxAge;
const jwe = await cookies.encrypt(originalSession, this.secret, expiration);
const cookieValue = jwe.toString();
const options = {
...this.cookieConfig,
maxAge
};
cookies.setChunkedCookie(this.sessionCookieName, cookieValue, options, reqCookies, resCookies);
// Store connection access tokens, each in its own cookie
if (connectionTokenSets?.length) {
await Promise.all(connectionTokenSets.map((connectionTokenSet, index) => this.storeInCookie(reqCookies, resCookies, connectionTokenSet, `${this.connectionTokenSetsCookieName}_${index}`, maxAge)));
}
// Any existing v3 cookie can be deleted as soon as we have set a v4 cookie.
// In stateless sessions, we do have to ensure we delete all chunks.
cookies.deleteChunkedCookie(LEGACY_COOKIE_NAME, reqCookies, resCookies, true, {
domain: this.cookieConfig.domain,
path: this.cookieConfig.path
});
}
async delete(reqCookies, resCookies) {
const deleteOptions = {
domain: this.cookieConfig.domain,
path: this.cookieConfig.path
};
cookies.deleteChunkedCookie(this.sessionCookieName, reqCookies, resCookies, false, deleteOptions);
this.getConnectionTokenSetsCookies(reqCookies).forEach((cookie) => cookies.deleteCookie(resCookies, cookie.name, deleteOptions));
}
async storeInCookie(reqCookies, resCookies, session, cookieName, maxAge) {
const expiration = Math.floor(Date.now() / 1000 + maxAge);
const jwe = await cookies.encrypt(session, this.secret, expiration);
const cookieValue = jwe.toString();
resCookies.set(cookieName, jwe.toString(), {
...this.cookieConfig,
maxAge
});
// to enable read-after-write in the same request for middleware
reqCookies.set(cookieName, cookieValue);
// check if the session cookie size exceeds 4096 bytes, and if so, log a warning
const cookieJarSizeTest = new cookies.ResponseCookies(new Headers());
cookieJarSizeTest.set(cookieName, cookieValue, {
...this.cookieConfig,
maxAge
});
if (new TextEncoder().encode(cookieJarSizeTest.toString()).length >= 4096) {
// if the cookie is the session cookie, log a warning with additional information about the claims and user profile.
if (cookieName === this.sessionCookieName) {
console.warn(`The ${cookieName} cookie size exceeds 4096 bytes, which may cause issues in some browsers. ` +
"Consider removing any unnecessary custom claims from the access token or the user profile. " +
"Alternatively, you can use a stateful session implementation to store the session data in a data store.");
}
else {
console.warn(`The ${cookieName} cookie size exceeds 4096 bytes, which may cause issues in some browsers. ` +
"You can use a stateful session implementation to store the session data in a data store.");
}
}
}
getConnectionTokenSetsCookies(cookies) {
return cookies
.getAll()
.filter((cookie) => cookie.name.startsWith(this.connectionTokenSetsCookieName));
}
}