From b40af03894473e4a4a6687f9499a9b1ca3df19a6 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 12 Feb 2025 12:18:22 +0100 Subject: [PATCH] auth-backend-module-github-provider: reject refresh for sessions without granted scope Signed-off-by: Patrik Oldsberg --- .changeset/chilled-kings-fold.md | 7 ++++++ .../src/authenticator.test.ts | 24 ++++++++++++++++--- .../src/authenticator.ts | 7 +++++- 3 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 .changeset/chilled-kings-fold.md diff --git a/.changeset/chilled-kings-fold.md b/.changeset/chilled-kings-fold.md new file mode 100644 index 0000000000..4f518e6ba3 --- /dev/null +++ b/.changeset/chilled-kings-fold.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-auth-backend-module-github-provider': patch +--- + +Fixed a bug where the requested scope was ignored when refreshing sessions for a GitHub OAuth App. This would lead to access tokens being returned that didn't have the requested scope, and in turn errors when trying to use these tokens. + +As part of this fix all existing sessions are being revoked in order to ensure that they receive the correct scope. diff --git a/plugins/auth-backend-module-github-provider/src/authenticator.test.ts b/plugins/auth-backend-module-github-provider/src/authenticator.test.ts index dc5c7208ea..48a297fa41 100644 --- a/plugins/auth-backend-module-github-provider/src/authenticator.test.ts +++ b/plugins/auth-backend-module-github-provider/src/authenticator.test.ts @@ -42,7 +42,7 @@ describe('githubAuthenticator', () => { accessToken: 'my-token', scope: 'user:read', tokenType: 'bearer', - refreshToken: 'access-token.my-token', + refreshToken: 'access-token-v2.my-token', }, }); }); @@ -105,9 +105,10 @@ describe('githubAuthenticator', () => { await expect( githubAuthenticator.refresh( { - refreshToken: 'access-token.my-token', + refreshToken: 'access-token-v2.my-token', req: {} as any, scope: 'user:read', + scopeAlreadyGranted: true, }, { fetchProfile: async _input => ({ id: 'id' } as PassportProfile), @@ -119,11 +120,28 @@ describe('githubAuthenticator', () => { accessToken: 'my-token', scope: 'user:read', tokenType: 'bearer', - refreshToken: 'access-token.my-token', + refreshToken: 'access-token-v2.my-token', }, }); }); + it('should fail refresh if scope has not already been granted', async () => { + await expect( + githubAuthenticator.refresh( + { + refreshToken: 'access-token-v2.my-token', + req: {} as any, + scope: 'user:read', + }, + { + fetchProfile: async _input => ({ id: 'id' } as PassportProfile), + } as PassportOAuthAuthenticatorHelper, + ), + ).rejects.toThrow( + 'Refresh failed, session has not been granted the requested scope', + ); + }); + it('should refresh with refresh token', async () => { const res = {}; await expect( diff --git a/plugins/auth-backend-module-github-provider/src/authenticator.ts b/plugins/auth-backend-module-github-provider/src/authenticator.ts index 7c94a04c30..e9dc29e4a6 100644 --- a/plugins/auth-backend-module-github-provider/src/authenticator.ts +++ b/plugins/auth-backend-module-github-provider/src/authenticator.ts @@ -22,7 +22,7 @@ import { PassportProfile, } from '@backstage/plugin-auth-node'; -const ACCESS_TOKEN_PREFIX = 'access-token.'; +const ACCESS_TOKEN_PREFIX = 'access-token-v2.'; /** @public */ export const githubAuthenticator = createOAuthAuthenticator({ @@ -98,6 +98,11 @@ export const githubAuthenticator = createOAuthAuthenticator({ // refresh token cookie. We use that token to fetch the user profile and // refresh the Backstage session when needed. if (input.refreshToken?.startsWith(ACCESS_TOKEN_PREFIX)) { + if (!input.scopeAlreadyGranted) { + throw new Error( + 'Refresh failed, session has not been granted the requested scope', + ); + } const accessToken = input.refreshToken.slice(ACCESS_TOKEN_PREFIX.length); const fullProfile = await helper