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:
@@ -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.
|
||||
@@ -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 };
|
||||
};
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user