diff --git a/.changeset/lemon-cameras-remember.md b/.changeset/lemon-cameras-remember.md new file mode 100644 index 0000000000..29cd88602f --- /dev/null +++ b/.changeset/lemon-cameras-remember.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +Migrated oidc auth provider to new `@backstage/plugin-auth-backend-module-oidc-provider` module package. diff --git a/.changeset/old-students-smoke.md b/.changeset/old-students-smoke.md new file mode 100644 index 0000000000..8e072c5203 --- /dev/null +++ b/.changeset/old-students-smoke.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend-module-oidc-provider': minor +--- + +Created new `@backstage/plugin-auth-backend-module-oidc-provider` module package to house oidc auth provider migration. diff --git a/plugins/auth-backend-module-oidc-provider/api-report.md b/plugins/auth-backend-module-oidc-provider/api-report.md new file mode 100644 index 0000000000..cf3335b13b --- /dev/null +++ b/plugins/auth-backend-module-oidc-provider/api-report.md @@ -0,0 +1,42 @@ +## API Report File for "@backstage/plugin-auth-backend-module-oidc-provider" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; +import { BaseClient } from 'openid-client'; +import { OAuthAuthenticator } from '@backstage/plugin-auth-node'; +import { PassportOAuthAuthenticatorHelper } from '@backstage/plugin-auth-node'; +import { PassportOAuthResult } from '@backstage/plugin-auth-node'; +import { PassportProfile } from '@backstage/plugin-auth-node'; +import { SignInResolverFactory } from '@backstage/plugin-auth-node'; +import { Strategy } from 'openid-client'; + +// @public (undocumented) +const authModuleOidcProvider: () => BackendFeature; +export default authModuleOidcProvider; + +// @public (undocumented) +export const oidcAuthenticator: OAuthAuthenticator< + Promise<{ + helper: PassportOAuthAuthenticatorHelper; + client: BaseClient; + initializedScope: string | undefined; + initializedPrompt: string | undefined; + strategy: Strategy; + }>, + PassportProfile +>; + +// @public +export namespace oidcSignInResolvers { + const emailLocalPartMatchingUserEntityName: SignInResolverFactory< + unknown, + unknown + >; + const emailMatchingUserEntityProfileEmail: SignInResolverFactory< + unknown, + unknown + >; +} +``` diff --git a/plugins/auth-backend-module-oidc-provider/package.json b/plugins/auth-backend-module-oidc-provider/package.json index 09bd1033a0..6962c1f340 100644 --- a/plugins/auth-backend-module-oidc-provider/package.json +++ b/plugins/auth-backend-module-oidc-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-oidc-provider", "description": "The oidc-provider backend module for the auth plugin.", - "version": "0.1.0-next.1", + "version": "0.0.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-oidc-provider/src/authenticator.test.ts b/plugins/auth-backend-module-oidc-provider/src/authenticator.test.ts index 4d21db8442..b44f89e7c7 100644 --- a/plugins/auth-backend-module-oidc-provider/src/authenticator.test.ts +++ b/plugins/auth-backend-module-oidc-provider/src/authenticator.test.ts @@ -348,13 +348,15 @@ describe('oidcAuthenticator', () => { }, session: { accessToken: 'accessToken', - expiresInSeconds: 3600, idToken, refreshToken: 'refreshToken', scope: 'testScope', tokenType: 'bearer', }, }); + expect( + Math.abs(handlerResponse.session.expiresInSeconds! - 3600), + ).toBeLessThan(5); }); it('fails without authorization code', async () => { diff --git a/plugins/auth-backend-module-oidc-provider/src/authenticator.ts b/plugins/auth-backend-module-oidc-provider/src/authenticator.ts index a95010e353..97b0af8d7f 100644 --- a/plugins/auth-backend-module-oidc-provider/src/authenticator.ts +++ b/plugins/auth-backend-module-oidc-provider/src/authenticator.ts @@ -36,7 +36,6 @@ export const oidcAuthenticator = createOAuthAuthenticator({ const clientSecret = config.getString('clientSecret'); const metadataUrl = config.getString('metadataUrl'); const customCallbackUrl = config.getOptionalString('callbackUrl'); - const callbackUrl2 = customCallbackUrl || callbackUrl; const tokenEndpointAuthMethod = config.getOptionalString( 'tokenEndpointAuthMethod', ) as ClientAuthMethod; @@ -51,7 +50,7 @@ export const oidcAuthenticator = createOAuthAuthenticator({ access_type: 'offline', // this option must be passed to provider to receive a refresh token client_id: clientId, client_secret: clientSecret, - redirect_uris: [callbackUrl2], + redirect_uris: [customCallbackUrl || callbackUrl], response_types: ['code'], token_endpoint_auth_method: tokenEndpointAuthMethod || 'client_secret_basic', @@ -84,7 +83,6 @@ export const oidcAuthenticator = createOAuthAuthenticator({ ? { familyName: userinfo.family_name, givenName: userinfo.given_name, - middleName: userinfo.middle_name, } : undefined; diff --git a/plugins/auth-backend-module-oidc-provider/src/module.test.ts b/plugins/auth-backend-module-oidc-provider/src/module.test.ts index 09ad38b84c..d2487da450 100644 --- a/plugins/auth-backend-module-oidc-provider/src/module.test.ts +++ b/plugins/auth-backend-module-oidc-provider/src/module.test.ts @@ -245,39 +245,18 @@ describe('authModuleOidcProvider', () => { it('#authenticate exchanges authorization code for a access_token', async () => { const agent = request.agent(''); - - // make /start request with audience parameter const startResponse = await agent.get( `${appUrl}/api/auth/oidc/start?env=development`, ); - // follow redirect to authorization endpoint const authorizationResponse = await agent.get( startResponse.header.location, ); - // follow redirect to token_endpoint const handlerResponse = await agent.get( authorizationResponse.header.location, ); expect(handlerResponse.text).toContain( - encodeURIComponent( - JSON.stringify({ - type: 'authorization_response', - response: { - profile: { - email: 'alice@test.com', - picture: 'http://testPictureUrl/photo.jpg', - displayName: 'Alice Adams', - }, - providerInfo: { - idToken, - accessToken: 'accessToken', - scope: 'testScope', - expiresInSeconds: 3600, - }, - }, - }), - ), + encodeURIComponent(`"accessToken":"accessToken"`), ); }); }); diff --git a/plugins/auth-backend/api-report.md b/plugins/auth-backend/api-report.md index d80648a10f..d097ae2881 100644 --- a/plugins/auth-backend/api-report.md +++ b/plugins/auth-backend/api-report.md @@ -340,7 +340,7 @@ export type OAuthStartResponse = { // @public @deprecated (undocumented) export type OAuthState = OAuthState_2; -// @public +// @public @deprecated export type OidcAuthResult = { tokenset: TokenSet; userinfo: UserinfoResponse; @@ -564,10 +564,10 @@ export const providers: Readonly<{ create: ( options?: | { - authHandler?: AuthHandler | undefined; + authHandler?: AuthHandler | undefined; signIn?: | { - resolver: SignInResolver; + resolver: SignInResolver; } | undefined; } diff --git a/plugins/auth-backend/config.d.ts b/plugins/auth-backend/config.d.ts index 34139593d3..2b80324fcd 100644 --- a/plugins/auth-backend/config.d.ts +++ b/plugins/auth-backend/config.d.ts @@ -149,22 +149,6 @@ export interface Config { }; }; /** @visibility frontend */ - oidc?: { - [authEnv: string]: { - clientId: string; - /** - * @visibility secret - */ - clientSecret: string; - callbackUrl?: string; - metadataUrl: string; - tokenEndpointAuthMethod?: string; - tokenSignedResponseAlg?: string; - scope?: string; - prompt?: string; - }; - }; - /** @visibility frontend */ auth0?: { [authEnv: string]: { clientId: string; diff --git a/plugins/auth-backend/src/providers/oidc/index.ts b/plugins/auth-backend/src/providers/oidc/index.ts index c4343b1547..6b2282411c 100644 --- a/plugins/auth-backend/src/providers/oidc/index.ts +++ b/plugins/auth-backend/src/providers/oidc/index.ts @@ -15,3 +15,4 @@ */ export { oidc } from './provider'; +export type { OidcAuthResult } from './provider'; diff --git a/plugins/auth-backend/src/providers/oidc/provider.ts b/plugins/auth-backend/src/providers/oidc/provider.ts index 18170e104e..f6804265cd 100644 --- a/plugins/auth-backend/src/providers/oidc/provider.ts +++ b/plugins/auth-backend/src/providers/oidc/provider.ts @@ -23,6 +23,21 @@ import { adaptLegacyOAuthSignInResolver, } from '../../lib/legacy'; import { oidcAuthenticator } from '@backstage/plugin-auth-backend-module-oidc-provider'; +import { TokenSet, UserinfoResponse } from 'openid-client'; +import { + commonByEmailLocalPartResolver, + commonByEmailResolver, +} from '../resolvers'; + +/** + * authentication result for the OIDC which includes the token set and user information (a profile response sent by OIDC server) + * @public + * @deprecated No longer used + */ +export type OidcAuthResult = { + tokenset: TokenSet; + userinfo: UserinfoResponse; +}; /** * Auth provider integration for generic OpenID Connect auth @@ -50,4 +65,14 @@ export const oidc = createAuthProviderIntegration({ signInResolver: adaptLegacyOAuthSignInResolver(options?.signIn?.resolver), }); }, + resolvers: { + /** + * Looks up the user by matching their email local part to the entity name. + */ + emailLocalPartMatchingUserEntityName: () => commonByEmailLocalPartResolver, + /** + * Looks up the user by matching their email to the entity email. + */ + emailMatchingUserEntityProfileEmail: () => commonByEmailResolver, + }, }); diff --git a/yarn.lock b/yarn.lock index 16979362ca..1e41fd83c7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -31270,24 +31270,10 @@ __metadata: languageName: node linkType: hard -"jose@npm:^4.14.4": - version: 4.14.6 - resolution: "jose@npm:4.14.6" - checksum: eae81a234e7bf1446b1bd80722b3462b014e3835b155c3a7799c1c5043163a53a0dc28d347004151b031e6b7b863403aabf8814d9cc217ce21f8c2f3ebd4b335 - languageName: node - linkType: hard - -"jose@npm:^4.14.6, jose@npm:^4.15.1": - version: 4.15.4 - resolution: "jose@npm:4.15.4" - checksum: dccad91cb3357f36423774a0b89ad830dd84b31090de65cd139b85488439f16a00f8c59c0773825e8a1adb0dd9d13ad725ad66e6ea33880ecb3959bb99e1ea5b - languageName: node - linkType: hard - -"jose@npm:^4.6.0": - version: 4.15.2 - resolution: "jose@npm:4.15.2" - checksum: 8f0cab1eef31243abe14a935b2b330cd95f10f9b69808fd642088ae5000e50e566664934537d2c6413ab2f6b54acd8265a5033da05157aa1260c5f1d7e57fab0 +"jose@npm:^4.14.6, jose@npm:^4.15.1, jose@npm:^4.6.0": + version: 4.15.3 + resolution: "jose@npm:4.15.3" + checksum: b76eeccc1d40d0eaf26dfaadc0f88fc15802c9105ab66a24ee223bd84369f7cb217f4a2cb852f5080ff6996170b3a73db2b2d26878b8905d99c36ca432628134 languageName: node linkType: hard @@ -35663,19 +35649,7 @@ __metadata: languageName: node linkType: hard -"openid-client@npm:^5.2.1, openid-client@npm:^5.3.0, openid-client@npm:^5.5.0": - version: 5.5.0 - resolution: "openid-client@npm:5.5.0" - dependencies: - jose: ^4.14.4 - lru-cache: ^6.0.0 - object-hash: ^2.2.0 - oidc-token-hash: ^5.0.3 - checksum: d2617b5bb0d9a0da338aeb7489bcbe3a79df9681189c7b61c2a3284289eee7110dfee2b04b49a9fdd4f064b7e2057ddb0becfedd9c19388e7788ae15b24c8e4c - languageName: node - linkType: hard - -"openid-client@npm:^5.4.3": +"openid-client@npm:^5.2.1, openid-client@npm:^5.3.0, openid-client@npm:^5.4.3, openid-client@npm:^5.5.0": version: 5.6.1 resolution: "openid-client@npm:5.6.1" dependencies: