diff --git a/.changeset/dull-bulldogs-learn.md b/.changeset/dull-bulldogs-learn.md new file mode 100644 index 0000000000..0892ecb6ba --- /dev/null +++ b/.changeset/dull-bulldogs-learn.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-defaults': patch +--- + +The user and plugin token verification in the default `AuthService` implementation will no longer forward verification errors to the caller, and instead log them as warnings. diff --git a/.changeset/forty-ravens-fry.md b/.changeset/forty-ravens-fry.md new file mode 100644 index 0000000000..8deb149441 --- /dev/null +++ b/.changeset/forty-ravens-fry.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-defaults': patch +--- + +The default `authServiceFactory` now correctly depends on the plugin scoped `Logger` services rather than the root scoped one. diff --git a/packages/backend-defaults/src/entrypoints/auth/authServiceFactory.test.ts b/packages/backend-defaults/src/entrypoints/auth/authServiceFactory.test.ts index 38b8862a4c..a244614135 100644 --- a/packages/backend-defaults/src/entrypoints/auth/authServiceFactory.test.ts +++ b/packages/backend-defaults/src/entrypoints/auth/authServiceFactory.test.ts @@ -109,8 +109,9 @@ describe('authServiceFactory', () => { }); it('should authenticate issued tokens with new auth', async () => { + const logger = mockServices.logger.mock(); const tester = ServiceFactoryTester.from(authServiceFactory, { - dependencies: mockDeps, + dependencies: [...mockDeps, logger.factory], }); const searchAuth = await tester.getSubject('search'); @@ -134,8 +135,13 @@ describe('authServiceFactory', () => { targetPluginId: 'catalog', }); + expect(logger.warn).not.toHaveBeenCalled(); await expect(searchAuth.authenticate(searchToken)).rejects.toThrow( - 'Invalid plugin token', + 'Failed plugin token verification', + ); + expect(logger.warn).toHaveBeenCalledWith( + 'Failed to verify incoming plugin token', + expect.any(Error), ); await expect(catalogAuth.authenticate(searchToken)).resolves.toEqual( expect.objectContaining({ diff --git a/packages/backend-defaults/src/entrypoints/auth/authServiceFactory.ts b/packages/backend-defaults/src/entrypoints/auth/authServiceFactory.ts index 129f0a8c95..91910292e6 100644 --- a/packages/backend-defaults/src/entrypoints/auth/authServiceFactory.ts +++ b/packages/backend-defaults/src/entrypoints/auth/authServiceFactory.ts @@ -59,7 +59,7 @@ export const authServiceFactory = createServiceFactory({ service: coreServices.auth, deps: { config: coreServices.rootConfig, - logger: coreServices.rootLogger, + logger: coreServices.logger, discovery: coreServices.discovery, plugin: coreServices.pluginMetadata, database: coreServices.database, @@ -89,6 +89,7 @@ export const authServiceFactory = createServiceFactory({ const userTokens = UserTokenHandler.create({ discovery, + logger, }); const pluginTokens = pluginTokenHandlerDecorator( diff --git a/packages/backend-defaults/src/entrypoints/auth/plugin/PluginTokenHandler.ts b/packages/backend-defaults/src/entrypoints/auth/plugin/PluginTokenHandler.ts index 281dca6d9e..9aec52bc08 100644 --- a/packages/backend-defaults/src/entrypoints/auth/plugin/PluginTokenHandler.ts +++ b/packages/backend-defaults/src/entrypoints/auth/plugin/PluginTokenHandler.ts @@ -120,7 +120,8 @@ export class DefaultPluginTokenHandler implements PluginTokenHandler { requiredClaims: ['iat', 'exp', 'sub', 'aud'], }, ).catch(e => { - throw new AuthenticationError('Invalid plugin token', e); + this.logger.warn('Failed to verify incoming plugin token', e); + throw new AuthenticationError('Failed plugin token verification'); }); return { subject: `plugin:${payload.sub}`, limitedUserToken: payload.obo }; diff --git a/packages/backend-defaults/src/entrypoints/auth/user/UserTokenHandler.test.ts b/packages/backend-defaults/src/entrypoints/auth/user/UserTokenHandler.test.ts index e85ec8db63..251e4c3adf 100644 --- a/packages/backend-defaults/src/entrypoints/auth/user/UserTokenHandler.test.ts +++ b/packages/backend-defaults/src/entrypoints/auth/user/UserTokenHandler.test.ts @@ -64,13 +64,17 @@ async function createToken(options: { describe('UserTokenHandler', () => { let userTokenHandler: UserTokenHandler; + const logger = mockServices.logger.mock(); + registerMswTestHooks(server); beforeEach(() => { jest.useRealTimers(); + jest.resetAllMocks(); userTokenHandler = UserTokenHandler.create({ discovery: mockServices.discovery(), + logger, }); server.use( @@ -120,7 +124,7 @@ describe('UserTokenHandler', () => { signature: 'sig', }), ), - ).rejects.toThrow('signature verification failed'); + ).rejects.toThrow('Failed user token verification'); await expect( userTokenHandler.verifyToken( @@ -130,7 +134,7 @@ describe('UserTokenHandler', () => { signature: 'sig', }), ), - ).rejects.toThrow('signature verification failed'); + ).rejects.toThrow('Failed user token verification'); }); it('should fail to verify tokens that have a bad alg', async () => { @@ -156,8 +160,13 @@ describe('UserTokenHandler', () => { }); const token = `${header}.${payload}.`; + expect(logger.warn).not.toHaveBeenCalled(); await expect(userTokenHandler.verifyToken(token)).rejects.toThrow( - /Unsupported "alg" value/, + 'Failed user token verification', + ); + expect(logger.warn).toHaveBeenCalledWith( + 'Failed to verify incoming user token', + expect.any(Error), ); }); diff --git a/packages/backend-defaults/src/entrypoints/auth/user/UserTokenHandler.ts b/packages/backend-defaults/src/entrypoints/auth/user/UserTokenHandler.ts index 4dd1874843..d205fa6b82 100644 --- a/packages/backend-defaults/src/entrypoints/auth/user/UserTokenHandler.ts +++ b/packages/backend-defaults/src/entrypoints/auth/user/UserTokenHandler.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { DiscoveryService } from '@backstage/backend-plugin-api'; +import { DiscoveryService, LoggerService } from '@backstage/backend-plugin-api'; import { AuthenticationError } from '@backstage/errors'; import { tokenTypes } from '@backstage/plugin-auth-node'; import { @@ -33,15 +33,21 @@ import { JwksClient } from '../JwksClient'; * @internal */ export class UserTokenHandler { - static create(options: { discovery: DiscoveryService }): UserTokenHandler { + static create(options: { + discovery: DiscoveryService; + logger: LoggerService; + }): UserTokenHandler { const jwksClient = new JwksClient(async () => { const url = await options.discovery.getBaseUrl('auth'); return new URL(`${url}/.well-known/jwks.json`); }); - return new UserTokenHandler(jwksClient); + return new UserTokenHandler(jwksClient, options.logger); } - constructor(private readonly jwksClient: JwksClient) {} + constructor( + private readonly jwksClient: JwksClient, + private readonly logger: LoggerService, + ) {} async verifyToken(token: string) { const verifyOpts = this.#getTokenVerificationOptions(token); @@ -57,7 +63,8 @@ export class UserTokenHandler { this.jwksClient.getKey, verifyOpts, ).catch(e => { - throw new AuthenticationError('Invalid token', e); + this.logger.warn('Failed to verify incoming user token', e); + throw new AuthenticationError('Failed user token verification'); }); const userEntityRef = payload.sub;