Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chunked cookies should not exceed browser max #301

Merged
merged 2 commits into from
Feb 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions src/auth0-session/cookie-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import { encryption as deriveKey } from './utils/hkdf';
import createDebug from './utils/debug';
import { getAll as getCookies, clear as clearCookie, set as setCookie } from './utils/cookies';
import { Config } from './config';
import { CookieSerializeOptions } from 'cookie';
import { CookieSerializeOptions, serialize } from 'cookie';

const debug = createDebug('cookie-store');
const epoch = (): number => (Date.now() / 1000) | 0; // eslint-disable-line no-bitwise
const CHUNK_BYTE_SIZE = 4000;
const MAX_COOKIE_SIZE = 4096;
const alg = 'dir';
const enc = 'A256GCM';

Expand All @@ -20,6 +20,8 @@ export default class CookieStore {

private currentKey: JWK.OctKey | undefined;

private chunkSize: number;

constructor(public config: Config) {
const secrets = Array.isArray(config.secret) ? config.secret : [config.secret];
this.keystore = new JWKS.KeyStore();
Expand All @@ -31,6 +33,20 @@ export default class CookieStore {
}
this.keystore.add(key);
});

const {
cookie: { transient, ...cookieConfig },
name: sessionName
} = this.config.session;
const cookieOptions: CookieSerializeOptions = {
...cookieConfig
};
if (!transient) {
cookieOptions.expires = new Date();
}

const emptyCookie = serialize(`${sessionName}.0`, '', cookieOptions);
this.chunkSize = MAX_COOKIE_SIZE - emptyCookie.length;
}

private encrypt(payload: string, headers: { [key: string]: any }): string {
Expand Down Expand Up @@ -172,11 +188,11 @@ export default class CookieStore {
debug('found session, creating signed session cookie(s) with name %o(.i)', sessionName);
const value = this.encrypt(JSON.stringify(session), { iat, uat, exp });

const chunkCount = Math.ceil(value.length / CHUNK_BYTE_SIZE);
const chunkCount = Math.ceil(value.length / this.chunkSize);
if (chunkCount > 1) {
debug('cookie size greater than %d, chunking', CHUNK_BYTE_SIZE);
debug('cookie size greater than %d, chunking', this.chunkSize);
for (let i = 0; i < chunkCount; i++) {
const chunkValue = value.slice(i * CHUNK_BYTE_SIZE, (i + 1) * CHUNK_BYTE_SIZE);
const chunkValue = value.slice(i * this.chunkSize, (i + 1) * this.chunkSize);
const chunkCookieName = `${sessionName}.${i}`;
setCookie(res, chunkCookieName, chunkValue, cookieOptions);
}
Expand Down
19 changes: 19 additions & 0 deletions tests/auth0-session/cookie-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,25 @@ describe('CookieStore', () => {
expect(session.claims).toHaveProperty('big_claim');
});

it('should limit total cookie size to 4096 Bytes', async () => {
const path =
'/some-really-really-really-really-really-really-really-really-really-really-really-really-really-long-path';
const baseURL = await setup({ ...defaultConfig, session: { cookie: { path } } });
const appSession = encrypted({
big_claim: randomBytes(5000).toString('base64')
});
expect(appSession.length).toBeGreaterThan(4096);
const cookieJar = toCookieJar({ appSession }, baseURL);
await get(baseURL, '/session', { cookieJar });
const cookies: { [key: string]: string } = cookieJar
.getCookiesSync(`${baseURL}${path}`)
.reduce((obj, value) => Object.assign(obj, { [value.key]: value + '' }), {});
expect(cookies['appSession.0']).toHaveLength(4096);
expect(cookies['appSession.1']).toHaveLength(4096);
expect(cookies['appSession.2']).toHaveLength(4096);
expect(cookies['appSession.3']).toHaveLength(1568);
});

it('should handle unordered chunked cookies', async () => {
const baseURL = await setup(defaultConfig);
const appSession = encrypted({ sub: '__chunked_sub__' });
Expand Down