diff --git a/.changeset/nervous-bears-brush.md b/.changeset/nervous-bears-brush.md new file mode 100644 index 0000000000..f3b0c7db78 --- /dev/null +++ b/.changeset/nervous-bears-brush.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +The Auth0 adapter no longer disables session refreshing. diff --git a/.changeset/unlucky-bobcats-fold.md b/.changeset/unlucky-bobcats-fold.md new file mode 100644 index 0000000000..6dd820db8d --- /dev/null +++ b/.changeset/unlucky-bobcats-fold.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': minor +--- + +Removed the explicit `disableRefresh` option from `OAuthAdapter`. Refresh can still be disabled for a provider by not implementing the `refresh` method. diff --git a/docs/auth/add-auth-provider.md b/docs/auth/add-auth-provider.md index 282088fef9..bd2bcd1694 100644 --- a/docs/auth/add-auth-provider.md +++ b/docs/auth/add-auth-provider.md @@ -153,7 +153,6 @@ export const createOktaProvider: AuthProviderFactory = ({ // Wrap the OAuthProviderHandlers with OAuthProvider, which implements AuthProviderRouteHandlers return OAuthProvider.fromConfig(globalConfig, provider, { - disableRefresh: false, providerId, tokenIssuer, }); diff --git a/plugins/auth-backend/api-report.md b/plugins/auth-backend/api-report.md index a38197668e..bb25ff6d57 100644 --- a/plugins/auth-backend/api-report.md +++ b/plugins/auth-backend/api-report.md @@ -586,11 +586,7 @@ export class OAuthAdapter implements AuthProviderRouteHandlers { handlers: OAuthHandlers, options: Pick< Options, - | 'providerId' - | 'persistScopes' - | 'disableRefresh' - | 'tokenIssuer' - | 'callbackUrl' + 'providerId' | 'persistScopes' | 'tokenIssuer' | 'callbackUrl' >, ): OAuthAdapter; // (undocumented) diff --git a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts index 0ce9dff66c..d6d346745a 100644 --- a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts +++ b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts @@ -60,7 +60,6 @@ describe('OAuthAdapter', () => { const oAuthProviderOptions = { providerId: 'test-provider', secure: false, - disableRefresh: true, appOrigin: 'http://localhost:3000', cookieDomain: 'example.com', cookiePath: '/auth/test-provider', @@ -110,7 +109,6 @@ describe('OAuthAdapter', () => { it('sets the refresh cookie if refresh is enabled', async () => { const oauthProvider = new OAuthAdapter(providerInstance, { ...oAuthProviderOptions, - disableRefresh: false, isOriginAllowed: () => false, }); @@ -153,7 +151,6 @@ describe('OAuthAdapter', () => { }; const oauthProvider = new OAuthAdapter(handlers, { ...oAuthProviderOptions, - disableRefresh: false, persistScopes: true, }); @@ -238,36 +235,9 @@ describe('OAuthAdapter', () => { ); }); - it('does not set the refresh cookie if refresh is disabled', async () => { - const oauthProvider = new OAuthAdapter(providerInstance, { - ...oAuthProviderOptions, - disableRefresh: true, - isOriginAllowed: () => false, - }); - - const mockRequest = { - cookies: { - 'test-provider-nonce': 'nonce', - }, - query: { - state: 'nonce', - }, - } as unknown as express.Request; - - const mockResponse = { - cookie: jest.fn().mockReturnThis(), - setHeader: jest.fn().mockReturnThis(), - end: jest.fn().mockReturnThis(), - } as unknown as express.Response; - - await oauthProvider.frameHandler(mockRequest, mockResponse); - expect(mockResponse.cookie).toHaveBeenCalledTimes(0); - }); - it('removes refresh cookie when logging out', async () => { const oauthProvider = new OAuthAdapter(providerInstance, { ...oAuthProviderOptions, - disableRefresh: false, isOriginAllowed: () => false, }); @@ -292,10 +262,8 @@ describe('OAuthAdapter', () => { }); it('gets new access-token when refreshing', async () => { - oAuthProviderOptions.disableRefresh = false; const oauthProvider = new OAuthAdapter(providerInstance, { ...oAuthProviderOptions, - disableRefresh: false, isOriginAllowed: () => false, }); @@ -327,26 +295,6 @@ describe('OAuthAdapter', () => { }); }); - it('handles refresh without capabilities', async () => { - const oauthProvider = new OAuthAdapter(providerInstance, { - ...oAuthProviderOptions, - disableRefresh: true, - isOriginAllowed: () => false, - }); - - const mockRequest = { - header: () => 'XMLHttpRequest', - } as unknown as express.Request; - - const mockResponse = {} as unknown as express.Response; - - await expect( - oauthProvider.refresh(mockRequest, mockResponse), - ).rejects.toThrow( - 'Refresh token is not supported for provider test-provider', - ); - }); - it('sets the correct cookie configuration using a callbackUrl', async () => { const config = { baseUrl: 'http://domain.org/auth', diff --git a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts index 637dc1cff2..5be4b7e3c0 100644 --- a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts +++ b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts @@ -48,7 +48,6 @@ export const TEN_MINUTES_MS = 600 * 1000; export type Options = { providerId: string; secure: boolean; - disableRefresh?: boolean; persistScopes?: boolean; cookieDomain: string; cookiePath: string; @@ -64,11 +63,7 @@ export class OAuthAdapter implements AuthProviderRouteHandlers { handlers: OAuthHandlers, options: Pick< Options, - | 'providerId' - | 'persistScopes' - | 'disableRefresh' - | 'tokenIssuer' - | 'callbackUrl' + 'providerId' | 'persistScopes' | 'tokenIssuer' | 'callbackUrl' >, ): OAuthAdapter { const { origin: appOrigin } = new URL(config.appUrl); @@ -170,7 +165,7 @@ export class OAuthAdapter implements AuthProviderRouteHandlers { response.providerInfo.scope = state.scope; } - if (refreshToken && !this.options.disableRefresh) { + if (refreshToken) { // set new refresh token this.setRefreshTokenCookie(res, refreshToken); } @@ -210,7 +205,7 @@ export class OAuthAdapter implements AuthProviderRouteHandlers { throw new AuthenticationError('Invalid X-Requested-With header'); } - if (!this.handlers.refresh || this.options.disableRefresh) { + if (!this.handlers.refresh) { throw new InputError( `Refresh token is not supported for provider ${this.options.providerId}`, ); diff --git a/plugins/auth-backend/src/providers/auth0/provider.ts b/plugins/auth-backend/src/providers/auth0/provider.ts index 3181314540..9ddd587508 100644 --- a/plugins/auth-backend/src/providers/auth0/provider.ts +++ b/plugins/auth-backend/src/providers/auth0/provider.ts @@ -243,7 +243,6 @@ export const auth0 = createAuthProviderIntegration({ }); return OAuthAdapter.fromConfig(globalConfig, provider, { - disableRefresh: true, providerId, callbackUrl, }); diff --git a/plugins/auth-backend/src/providers/bitbucket/provider.ts b/plugins/auth-backend/src/providers/bitbucket/provider.ts index 905181333a..5b9919764b 100644 --- a/plugins/auth-backend/src/providers/bitbucket/provider.ts +++ b/plugins/auth-backend/src/providers/bitbucket/provider.ts @@ -261,7 +261,6 @@ export const bitbucket = createAuthProviderIntegration({ }); return OAuthAdapter.fromConfig(globalConfig, provider, { - disableRefresh: false, providerId, callbackUrl, }); diff --git a/plugins/auth-backend/src/providers/gitlab/provider.ts b/plugins/auth-backend/src/providers/gitlab/provider.ts index 23867f6990..868357bcda 100644 --- a/plugins/auth-backend/src/providers/gitlab/provider.ts +++ b/plugins/auth-backend/src/providers/gitlab/provider.ts @@ -248,7 +248,6 @@ export const gitlab = createAuthProviderIntegration({ }); return OAuthAdapter.fromConfig(globalConfig, provider, { - disableRefresh: false, providerId, callbackUrl, }); diff --git a/plugins/auth-backend/src/providers/google/provider.ts b/plugins/auth-backend/src/providers/google/provider.ts index 233e28f750..2d58bc2aa6 100644 --- a/plugins/auth-backend/src/providers/google/provider.ts +++ b/plugins/auth-backend/src/providers/google/provider.ts @@ -238,7 +238,6 @@ export const google = createAuthProviderIntegration({ }); return OAuthAdapter.fromConfig(globalConfig, provider, { - disableRefresh: false, providerId, callbackUrl, }); diff --git a/plugins/auth-backend/src/providers/microsoft/provider.ts b/plugins/auth-backend/src/providers/microsoft/provider.ts index 0a4b67d667..0c58049619 100644 --- a/plugins/auth-backend/src/providers/microsoft/provider.ts +++ b/plugins/auth-backend/src/providers/microsoft/provider.ts @@ -267,7 +267,6 @@ export const microsoft = createAuthProviderIntegration({ }); return OAuthAdapter.fromConfig(globalConfig, provider, { - disableRefresh: false, providerId, callbackUrl, }); diff --git a/plugins/auth-backend/src/providers/oauth2/provider.ts b/plugins/auth-backend/src/providers/oauth2/provider.ts index 8e0250cf2f..6b4787e985 100644 --- a/plugins/auth-backend/src/providers/oauth2/provider.ts +++ b/plugins/auth-backend/src/providers/oauth2/provider.ts @@ -43,6 +43,7 @@ import { SignInResolver, } from '../types'; import { createAuthProviderIntegration } from '../createAuthProviderIntegration'; +import { InputError } from '@backstage/errors'; type PrivateInfo = { refreshToken: string; @@ -56,6 +57,7 @@ export type OAuth2AuthProviderOptions = OAuthProviderOptions & { scope?: string; resolverContext: AuthResolverContext; includeBasicAuth?: boolean; + disableRefresh?: boolean; }; export class OAuth2AuthProvider implements OAuthHandlers { @@ -63,11 +65,13 @@ export class OAuth2AuthProvider implements OAuthHandlers { private readonly signInResolver?: SignInResolver; private readonly authHandler: AuthHandler; private readonly resolverContext: AuthResolverContext; + private readonly disableRefresh: boolean; constructor(options: OAuth2AuthProviderOptions) { this.signInResolver = options.signInResolver; this.authHandler = options.authHandler; this.resolverContext = options.resolverContext; + this.disableRefresh = options.disableRefresh ?? false; this._strategy = new OAuth2Strategy( { @@ -132,6 +136,9 @@ export class OAuth2AuthProvider implements OAuthHandlers { } async refresh(req: OAuthRefreshRequest) { + if (this.disableRefresh) { + throw new InputError('Session refreshes have been disabled'); + } const refreshTokenResponse = await executeRefreshTokenStrategy( this._strategy, req.refreshToken, @@ -243,10 +250,10 @@ export const oauth2 = createAuthProviderIntegration({ scope, includeBasicAuth, resolverContext, + disableRefresh, }); return OAuthAdapter.fromConfig(globalConfig, provider, { - disableRefresh, providerId, callbackUrl, }); diff --git a/plugins/auth-backend/src/providers/oidc/provider.ts b/plugins/auth-backend/src/providers/oidc/provider.ts index aba9048cc7..69a65d1cae 100644 --- a/plugins/auth-backend/src/providers/oidc/provider.ts +++ b/plugins/auth-backend/src/providers/oidc/provider.ts @@ -262,7 +262,6 @@ export const oidc = createAuthProviderIntegration({ }); return OAuthAdapter.fromConfig(globalConfig, provider, { - disableRefresh: false, providerId, callbackUrl, }); diff --git a/plugins/auth-backend/src/providers/okta/provider.ts b/plugins/auth-backend/src/providers/okta/provider.ts index 4e05ff4487..98cec6df3a 100644 --- a/plugins/auth-backend/src/providers/okta/provider.ts +++ b/plugins/auth-backend/src/providers/okta/provider.ts @@ -271,7 +271,6 @@ export const okta = createAuthProviderIntegration({ }); return OAuthAdapter.fromConfig(globalConfig, provider, { - disableRefresh: false, providerId, callbackUrl, }); diff --git a/plugins/auth-backend/src/providers/onelogin/provider.ts b/plugins/auth-backend/src/providers/onelogin/provider.ts index 7dcc6455ac..d81eb3d83c 100644 --- a/plugins/auth-backend/src/providers/onelogin/provider.ts +++ b/plugins/auth-backend/src/providers/onelogin/provider.ts @@ -238,7 +238,6 @@ export const onelogin = createAuthProviderIntegration({ }); return OAuthAdapter.fromConfig(globalConfig, provider, { - disableRefresh: false, providerId, callbackUrl, });