From e54fcb2ed581d0b9d308598c3f9e0bb0b8724f80 Mon Sep 17 00:00:00 2001 From: Chris Kilding <56678532+chriskilding-relx@users.noreply.github.com> Date: Tue, 30 Sep 2025 15:46:57 +0100 Subject: [PATCH 1/2] Support custom start URL search parameters in OIDC provider Signed-off-by: Chris Kilding <56678532+chriskilding-relx@users.noreply.github.com> --- .changeset/proud-streets-switch.md | 5 ++ docs/auth/oidc.md | 6 ++ .../config.d.ts | 1 + .../report.api.md | 1 + .../src/authenticator.test.ts | 56 +++++++++++++++++++ .../src/authenticator.ts | 11 +++- 6 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 .changeset/proud-streets-switch.md diff --git a/.changeset/proud-streets-switch.md b/.changeset/proud-streets-switch.md new file mode 100644 index 0000000000..081a1e7b3a --- /dev/null +++ b/.changeset/proud-streets-switch.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend-module-oidc-provider': minor +--- + +Added support for custom start URL search parameters (with the new `startUrlSearchParams` config property) diff --git a/docs/auth/oidc.md b/docs/auth/oidc.md index 7c4b09d9bf..55827ab1dc 100644 --- a/docs/auth/oidc.md +++ b/docs/auth/oidc.md @@ -241,6 +241,12 @@ These parameters have implicit default values. Don't override them unless you kn - `prompt`: Recommended to use `auto` so the browser will request sign-in to the IDP if the user has no active session. - `sessionDuration`: Lifespan of the user session. +- `startUrlSearchParams`: This is a dictionary of search (query) parameters for the OIDC + authorization start URL. Don't define it unless you want to change the identity + provider's behavior. (For example, you could set the `organization` parameter to guide + users towards a particular sign-in option that your organization prefers.) **Note:** the + start URL is controlled by the browser, so this feature is only for improving the + Backstage user experience. :::note Config Reloading Backstage does not yet support hot reloading of auth provider configuration. Any changes to this YAML file require a restart of Backstage. diff --git a/plugins/auth-backend-module-oidc-provider/config.d.ts b/plugins/auth-backend-module-oidc-provider/config.d.ts index c11b9319ec..f25d13ae2e 100644 --- a/plugins/auth-backend-module-oidc-provider/config.d.ts +++ b/plugins/auth-backend-module-oidc-provider/config.d.ts @@ -33,6 +33,7 @@ export interface Config { additionalScopes?: string | string[]; prompt?: string; timeout?: HumanDuration | string; + startUrlSearchParams?: [string: string]; signIn?: { resolvers: Array< | { diff --git a/plugins/auth-backend-module-oidc-provider/report.api.md b/plugins/auth-backend-module-oidc-provider/report.api.md index 11cde22db1..ab7d2bda49 100644 --- a/plugins/auth-backend-module-oidc-provider/report.api.md +++ b/plugins/auth-backend-module-oidc-provider/report.api.md @@ -25,6 +25,7 @@ export const oidcAuthenticator: OAuthAuthenticator< client: BaseClient; strategy: Strategy; }>; + searchParams: Record; }, OidcAuthResult >; 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 c327d4ecc5..1940a71082 100644 --- a/plugins/auth-backend-module-oidc-provider/src/authenticator.test.ts +++ b/plugins/auth-backend-module-oidc-provider/src/authenticator.test.ts @@ -279,6 +279,62 @@ describe('oidcAuthenticator', () => { expect(searchParams.get('response_type')).toBe('code'); }); + it('passes custom start URL search parameters', async () => { + const customImplementation = oidcAuthenticator.initialize({ + callbackUrl: 'https://backstage.test/callback', + config: new ConfigReader({ + metadataUrl: 'https://oidc.test/.well-known/openid-configuration', + clientId: 'clientId123', + clientSecret: 'clientSecret', + startUrlSearchParams: { + foo: '1', + bar: '2', + }, + }), + }); + + const startResponse = await oidcAuthenticator.start( + startRequest, + customImplementation, + ); + + const { searchParams } = new URL(startResponse.url); + + expect(searchParams.get('foo')).toBe('1'); + expect(searchParams.get('bar')).toBe('2'); + }); + + it('does not override the core start URL search parameters with custom ones', async () => { + const customImplementation = oidcAuthenticator.initialize({ + callbackUrl: 'https://backstage.test/callback', + config: new ConfigReader({ + metadataUrl: 'https://oidc.test/.well-known/openid-configuration', + clientId: 'clientId123', + clientSecret: 'clientSecret', + startUrlSearchParams: { + foo: '1', + prompt: 'customPrompt', + scope: 'customScope', + state: 'customState', + nonce: 'customNonce', + }, + }), + }); + + const startResponse = await oidcAuthenticator.start( + startRequest, + customImplementation, + ); + + const { searchParams } = new URL(startResponse.url); + + expect(searchParams.get('foo')).toBe('1'); + expect(searchParams.get('scope')).not.toBe('customScope'); + expect(searchParams.get('state')).not.toBe('customState'); + expect(searchParams.get('nonce')).not.toBe('customNonce'); + expect(searchParams.get('prompt')).not.toBe('customPrompt'); + }); + it('passes a nonce', async () => { const startResponse = await oidcAuthenticator.start( startRequest, diff --git a/plugins/auth-backend-module-oidc-provider/src/authenticator.ts b/plugins/auth-backend-module-oidc-provider/src/authenticator.ts index 6e7022f7cf..316ead3954 100644 --- a/plugins/auth-backend-module-oidc-provider/src/authenticator.ts +++ b/plugins/auth-backend-module-oidc-provider/src/authenticator.ts @@ -83,6 +83,9 @@ export const oidcAuthenticator = createOAuthAuthenticator({ ); const initializedPrompt = config.getOptionalString('prompt'); + const startUrlSearchParams: Record = + config.getOptional('startUrlSearchParams') || {}; + if (config.has('scope')) { throw new Error( 'The oidc provider no longer supports the "scope" configuration option. Please use the "additionalScopes" option instead.', @@ -143,17 +146,21 @@ export const oidcAuthenticator = createOAuthAuthenticator({ return { helper, client, strategy }; }); - return { initializedPrompt, promise }; + return { initializedPrompt, promise, searchParams: startUrlSearchParams }; }, async start(input, ctx) { - const { initializedPrompt, promise } = ctx; + const { initializedPrompt, promise, searchParams } = ctx; const { helper } = await promise; + + // Merge the custom start URL params, but do not override the standard params (scope, state etc) const options: Record = { + ...searchParams, scope: input.scope, state: input.state, nonce: crypto.randomBytes(16).toString('base64'), }; + const prompt = initializedPrompt || 'none'; if (prompt !== 'auto') { options.prompt = prompt; From da706d64f89969cd9e6c86a093c8cd479000efb1 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 20 Dec 2025 11:50:17 +0100 Subject: [PATCH 2/2] Update .changeset/proud-streets-switch.md Signed-off-by: Patrik Oldsberg --- .changeset/proud-streets-switch.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/proud-streets-switch.md b/.changeset/proud-streets-switch.md index 081a1e7b3a..188b66f178 100644 --- a/.changeset/proud-streets-switch.md +++ b/.changeset/proud-streets-switch.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-auth-backend-module-oidc-provider': minor +'@backstage/plugin-auth-backend-module-oidc-provider': patch --- Added support for custom start URL search parameters (with the new `startUrlSearchParams` config property)