Skip to content

Commit 390cecb

Browse files
author
Raphael Kabo
committed
fix: standardise cookie settings
1 parent bfec490 commit 390cecb

File tree

4 files changed

+39
-51
lines changed

4 files changed

+39
-51
lines changed

server/__tests__/middleware/identityMiddleware.test.ts

+10-12
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { Request, Response } from 'express';
66
import { conf } from '@/server/config';
77
import * as identityMiddleware from '@/server/middleware/identityMiddleware';
88
import {
9+
oauthCookieOptions,
910
performAuthorizationCodeFlow,
1011
verifyAccessToken,
1112
verifyIdToken,
@@ -148,6 +149,7 @@ describe('withOAuth middleware', () => {
148149
});
149150
it('returns next() if the route does not require sign in', async () => {
150151
const req = {
152+
cookies: {},
151153
signedCookies: {},
152154
originalUrl: '/help-centre',
153155
};
@@ -184,18 +186,14 @@ describe('withOAuth middleware', () => {
184186
next,
185187
);
186188

187-
expect(res.clearCookie).toHaveBeenCalledWith('GU_ACCESS_TOKEN', {
188-
signed: true,
189-
secure: true,
190-
httpOnly: true,
191-
sameSite: 'strict',
192-
});
193-
expect(res.clearCookie).toHaveBeenCalledWith('GU_ID_TOKEN', {
194-
signed: true,
195-
secure: true,
196-
httpOnly: true,
197-
sameSite: 'strict',
198-
});
189+
expect(res.clearCookie).toHaveBeenCalledWith(
190+
'GU_ACCESS_TOKEN',
191+
oauthCookieOptions,
192+
);
193+
expect(res.clearCookie).toHaveBeenCalledWith(
194+
'GU_ID_TOKEN',
195+
oauthCookieOptions,
196+
);
199197
expect(performAuthorizationCodeFlow).toHaveBeenCalledWith(req, res, {
200198
redirectUri: `https://manage.${conf.DOMAIN}/oauth/callback`,
201199
scopes,

server/middleware/identityMiddleware.ts

+17-25
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { NextFunction, Request, Response } from 'express';
33
import type { Scopes, VerifiedOAuthCookies } from '@/server/oauth';
44
import {
55
OAuthAccessTokenCookieName,
6+
oauthCookieOptions,
67
OAuthIdTokenCookieName,
78
performAuthorizationCodeFlow,
89
scopes,
@@ -98,6 +99,22 @@ export const withOAuth = async (
9899
if (CYPRESS === 'SKIP_IDAPI') {
99100
return next();
100101
}
102+
103+
// Get the path of the current page and use it as our returnPath after the OAuth callback.
104+
const returnPath = sanitizeReturnPath(req.originalUrl);
105+
106+
// If we have a GU_SO cookie, we've signed out recently, so we need to delete
107+
// the access and ID tokens from the browser and redirect to the sign in page.
108+
if (req.cookies['GU_SO']) {
109+
res.clearCookie(OAuthAccessTokenCookieName, oauthCookieOptions);
110+
res.clearCookie(OAuthIdTokenCookieName, oauthCookieOptions);
111+
return performAuthorizationCodeFlow(req, res, {
112+
redirectUri: `https://manage.${conf.DOMAIN}/oauth/callback`,
113+
scopes,
114+
returnPath,
115+
});
116+
}
117+
101118
// Is this a public route?
102119
if (!requiresSignin(req.originalUrl)) {
103120
// If we do have access and ID token cookies, we can attempt to verify them
@@ -115,31 +132,6 @@ export const withOAuth = async (
115132
}
116133
}
117134

118-
// Get the path of the current page and use it as our returnPath after the OAuth callback.
119-
const returnPath = sanitizeReturnPath(req.originalUrl);
120-
121-
// If we have a GU_SO cookie, we've signed out recently, so we need to delete
122-
// the access and ID tokens from the browser and redirect to the sign in page.
123-
if (req.cookies['GU_SO']) {
124-
res.clearCookie(OAuthAccessTokenCookieName, {
125-
signed: true,
126-
secure: true,
127-
httpOnly: true,
128-
sameSite: 'strict',
129-
});
130-
res.clearCookie(OAuthIdTokenCookieName, {
131-
signed: true,
132-
secure: true,
133-
httpOnly: true,
134-
sameSite: 'strict',
135-
});
136-
return performAuthorizationCodeFlow(req, res, {
137-
redirectUri: `https://manage.${conf.DOMAIN}/oauth/callback`,
138-
scopes,
139-
returnPath,
140-
});
141-
}
142-
143135
try {
144136
const { accessToken, idToken } = await verifyOAuthCookies(req);
145137

server/oauth.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ export interface VerifiedOAuthCookies {
2020
idToken?: OktaJwtVerifier.Jwt;
2121
}
2222

23+
export const oauthCookieOptions: CookieOptions = {
24+
signed: true,
25+
secure: true,
26+
httpOnly: true,
27+
sameSite: 'strict',
28+
};
29+
2330
const sharedTokenVerifierOptions = (
2431
oktaConfig: OktaConfig,
2532
): OktaJwtVerifier.VerifierOptions => ({
@@ -190,10 +197,8 @@ export const performAuthorizationCodeFlow = async (
190197
};
191198
const encodedState = Buffer.from(JSON.stringify(state)).toString('base64');
192199
res.cookie(OAuthStateCookieName, encodedState, {
193-
httpOnly: true,
200+
...oauthCookieOptions,
194201
maxAge: ms('10m'),
195-
secure: true,
196-
signed: true,
197202
});
198203

199204
// generate the /authorize endpoint url which we'll redirect the user to

server/routes/oauth.ts

+4-11
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import ms from 'ms';
44
import {
55
exchangeAccessTokenForCookies,
66
ManageMyAccountOpenIdClient,
7+
oauthCookieOptions,
78
OAuthStateCookieName,
89
setIDAPICookies,
910
} from '@/server/oauth';
@@ -63,24 +64,16 @@ router.get('/callback', async (req: Request, res: Response) => {
6364

6465
// Set the access token and ID tokens as cookies
6566
res.cookie('GU_ACCESS_TOKEN', tokenSet.access_token, {
66-
signed: true,
67-
secure: true,
68-
httpOnly: true,
67+
...oauthCookieOptions,
6968
maxAge: ms('30m'), // Same expiry as set in Okta
7069
});
7170
res.cookie('GU_ID_TOKEN', tokenSet.id_token, {
72-
signed: true,
73-
secure: true,
74-
httpOnly: true,
71+
...oauthCookieOptions,
7572
maxAge: ms('30m'), // Same expiry as set in Okta
7673
});
7774

7875
// Delete state cookie, for it is no longer needed
79-
res.clearCookie(OAuthStateCookieName, {
80-
httpOnly: true,
81-
secure: true,
82-
signed: true,
83-
});
76+
res.clearCookie(OAuthStateCookieName, oauthCookieOptions);
8477

8578
// Exchange the access token for IDAPI cookies. To maintain dual
8679
// running of new, OAuth-powered API routes and 'classic' IDAPI

0 commit comments

Comments
 (0)