Add optional SameSite attribute to CookieConfigurer and return from defaultCookieConfigurer for secure contexts.

Signed-off-by: Marcus Eide <eide@spotify.com>
This commit is contained in:
Marcus Eide
2022-09-06 13:12:50 +02:00
parent 3612e97174
commit 5fa831ce55
7 changed files with 57 additions and 19 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-auth-backend': patch
---
Allow CookieConfigurer to optionally return the SameSite cookie attribute. Return `SameSite=None` in `defaultCookieConfigurer` for secure contexts to allow cookies to be included in third-party requests.
+2 -3
View File
@@ -187,6 +187,7 @@ export type CookieConfigurer = (ctx: {
domain: string;
path: string;
secure: boolean;
sameSite?: 'none' | 'lax' | 'strict';
};
// @public
@@ -280,11 +281,9 @@ export class OAuthAdapter implements AuthProviderRouteHandlers {
// @public (undocumented)
export type OAuthAdapterOptions = {
providerId: string;
secure: boolean;
persistScopes?: boolean;
cookieDomain: string;
cookiePath: string;
appOrigin: string;
cookieConfig: ReturnType<CookieConfigurer>;
isOriginAllowed: (origin: string) => boolean;
callbackUrl: string;
};
@@ -18,6 +18,7 @@ import express from 'express';
import { THOUSAND_DAYS_MS, TEN_MINUTES_MS, OAuthAdapter } from './OAuthAdapter';
import { encodeState } from './helpers';
import { OAuthHandlers, OAuthState } from './types';
import { CookieConfigurer } from '../../providers/types';
const mockResponseData = {
providerInfo: {
@@ -57,12 +58,17 @@ describe('OAuthAdapter', () => {
}
}
const providerInstance = new MyAuthProvider();
const cookieConfig = {
domain: 'example.com',
path: '/auth/test-provider',
secure: false,
sameSite: 'lax',
} as ReturnType<CookieConfigurer>;
const oAuthProviderOptions = {
providerId: 'test-provider',
secure: false,
appOrigin: 'http://localhost:3000',
cookieDomain: 'example.com',
cookiePath: '/auth/test-provider',
cookieConfig,
tokenIssuer: {
issueToken: async () => 'my-id-token',
listPublicKeys: async () => ({ keys: [] }),
@@ -136,6 +142,8 @@ describe('OAuthAdapter', () => {
expect.objectContaining({
path: '/auth/test-provider',
maxAge: THOUSAND_DAYS_MS,
secure: false,
sameSite: 'lax',
}),
);
});
@@ -331,6 +339,7 @@ describe('OAuthAdapter', () => {
domain: 'authdomain.org',
path: '/auth/test-provider/handler',
secure: true,
sameSite: 'none',
}),
);
});
@@ -24,6 +24,7 @@ import {
import {
AuthProviderRouteHandlers,
AuthProviderConfig,
CookieConfigurer,
} from '../../providers/types';
import {
AuthenticationError,
@@ -47,11 +48,9 @@ export const TEN_MINUTES_MS = 600 * 1000;
/** @public */
export type OAuthAdapterOptions = {
providerId: string;
secure: boolean;
persistScopes?: boolean;
cookieDomain: string;
cookiePath: string;
appOrigin: string;
cookieConfig: ReturnType<CookieConfigurer>;
isOriginAllowed: (origin: string) => boolean;
callbackUrl: string;
};
@@ -78,9 +77,7 @@ export class OAuthAdapter implements AuthProviderRouteHandlers {
return new OAuthAdapter(handlers, {
...options,
appOrigin,
cookieDomain: cookieConfig.domain,
cookiePath: cookieConfig.path,
secure: cookieConfig.secure,
cookieConfig,
isOriginAllowed: config.isOriginAllowed,
});
}
@@ -93,10 +90,7 @@ export class OAuthAdapter implements AuthProviderRouteHandlers {
) {
this.baseCookieOptions = {
httpOnly: true,
sameSite: 'lax',
secure: this.options.secure,
path: this.options.cookiePath,
domain: this.options.cookieDomain,
...this.options.cookieConfig,
};
}
@@ -265,7 +259,7 @@ export class OAuthAdapter implements AuthProviderRouteHandlers {
res.cookie(`${this.options.providerId}-nonce`, nonce, {
maxAge: TEN_MINUTES_MS,
...this.baseCookieOptions,
path: `${this.options.cookiePath}/handler`,
path: `${this.options.cookieConfig.path}/handler`,
});
};
@@ -150,5 +150,30 @@ describe('OAuthProvider Utils', () => {
secure: true,
});
});
it('should set sameSite to none for https', () => {
expect(
defaultCookieConfigurer({
baseUrl: '',
providerId: 'test-provider',
callbackUrl: 'https://domain.org/auth',
}),
).toMatchObject({
sameSite: 'none',
secure: true,
});
});
it('should set sameSite to lax for http', () => {
expect(
defaultCookieConfigurer({
baseUrl: '',
providerId: 'test-provider',
callbackUrl: 'http://domain.org/auth',
}),
).toMatchObject({
sameSite: 'lax',
secure: false,
});
});
});
});
@@ -68,6 +68,7 @@ export const defaultCookieConfigurer: CookieConfigurer = ({
}) => {
const { hostname: domain, pathname, protocol } = new URL(callbackUrl);
const secure = protocol === 'https:';
const sameSite = secure ? 'none' : 'lax';
// If the provider supports callbackUrls, the pathname will
// contain the complete path to the frame handler so we need
@@ -76,5 +77,5 @@ export const defaultCookieConfigurer: CookieConfigurer = ({
? pathname.slice(0, -'/handler/frame'.length)
: `${pathname}/${providerId}`;
return { domain, path, secure };
return { domain, path, secure, sameSite };
};
+6 -1
View File
@@ -100,7 +100,12 @@ export type CookieConfigurer = (ctx: {
baseUrl: string;
/** The configured callback URL of the auth provider */
callbackUrl: string;
}) => { domain: string; path: string; secure: boolean };
}) => {
domain: string;
path: string;
secure: boolean;
sameSite?: 'none' | 'lax' | 'strict';
};
/** @public */
export type AuthProviderConfig = {