diff --git a/plugins/auth-backend-module-auth0-provider/config.d.ts b/plugins/auth-backend-module-auth0-provider/config.d.ts index 661409a592..d2b1c66389 100644 --- a/plugins/auth-backend-module-auth0-provider/config.d.ts +++ b/plugins/auth-backend-module-auth0-provider/config.d.ts @@ -32,6 +32,7 @@ export interface Config { audience?: string; connection?: string; connectionScope?: string; + organization?: string; sessionDuration?: HumanDuration | string; }; }; diff --git a/plugins/auth-backend-module-auth0-provider/package.json b/plugins/auth-backend-module-auth0-provider/package.json index 8b749bc420..7ca877a6a2 100644 --- a/plugins/auth-backend-module-auth0-provider/package.json +++ b/plugins/auth-backend-module-auth0-provider/package.json @@ -37,6 +37,7 @@ "@backstage/backend-plugin-api": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", "express": "^4.22.0", + "passport": "^0.7.0", "passport-auth0": "^1.4.3", "passport-oauth2": "^1.6.1" }, @@ -46,6 +47,7 @@ "@backstage/cli": "workspace:^", "@backstage/plugin-auth-backend": "workspace:^", "@backstage/types": "workspace:^", + "@types/passport": "^1.0.3", "@types/passport-auth0": "^1.0.5", "@types/passport-oauth2": "^1.4.15", "supertest": "^7.0.0" diff --git a/plugins/auth-backend-module-auth0-provider/src/authenticator.ts b/plugins/auth-backend-module-auth0-provider/src/authenticator.ts index a042f113f8..5ed5fc580c 100644 --- a/plugins/auth-backend-module-auth0-provider/src/authenticator.ts +++ b/plugins/auth-backend-module-auth0-provider/src/authenticator.ts @@ -35,6 +35,7 @@ export const auth0Authenticator = createOAuthAuthenticator({ const connection = config.getOptionalString('connection'); const connectionScope = config.getOptionalString('connectionScope'); const callbackURL = config.getOptionalString('callbackUrl') ?? callbackUrl; + const organization = config.getOptionalString('organization'); // Due to passport-auth0 forcing options.state = true, // passport-oauth2 requires express-session to be installed // so that the 'state' parameter of the oauth2 flow can be stored. @@ -58,6 +59,7 @@ export const auth0Authenticator = createOAuthAuthenticator({ callbackURL, domain, store, + organization, // We need passReqToCallback set to false to get params, but there's // no matching type signature for that, so instead behold this beauty passReqToCallback: false as true, diff --git a/plugins/auth-backend-module-auth0-provider/src/strategy.ts b/plugins/auth-backend-module-auth0-provider/src/strategy.ts index 039885dddb..a8699555a7 100644 --- a/plugins/auth-backend-module-auth0-provider/src/strategy.ts +++ b/plugins/auth-backend-module-auth0-provider/src/strategy.ts @@ -25,10 +25,13 @@ export interface Auth0StrategyOptionsWithRequest { domain: string; passReqToCallback: true; store: StateStore; + organization?: string; } /** @public */ export class Auth0Strategy extends Auth0InternalStrategy { + private organization: string | undefined; + constructor( options: Auth0StrategyOptionsWithRequest, verify: Auth0InternalStrategy.VerifyFunction, @@ -41,5 +44,18 @@ export class Auth0Strategy extends Auth0InternalStrategy { apiUrl: `https://${options.domain}/api`, }; super(optionsWithURLs, verify); + this.organization = options.organization; + } + + authorizationParams(options: Record): Record { + const params = super.authorizationParams(options); + + if (this.organization) { + return { + ...params, + organization: this.organization, + }; + } + return params; } } diff --git a/plugins/auth-backend-module-auth0-provider/src/types.d.ts b/plugins/auth-backend-module-auth0-provider/src/types.d.ts new file mode 100644 index 0000000000..f0efc49347 --- /dev/null +++ b/plugins/auth-backend-module-auth0-provider/src/types.d.ts @@ -0,0 +1,100 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +declare module 'passport-auth0' { + import passport from 'passport'; + import express from 'express'; + + declare class StrategyInternal extends passport.Strategy { + constructor( + options: StrategyInternal.StrategyOptionWithRequest, + verify: StrategyInternal.VerifyFunctionWithRequest, + ); + constructor( + options: StrategyInternal.StrategyOption, + verify: StrategyInternal.VerifyFunction, + ); + + name: string; + authenticate(req: express.Request, options?: object): void; + authorizationParams(options: Record): Record; + } + + declare namespace StrategyInternal { + interface Profile extends passport.Profile { + id: string; + displayName: string; + gender?: string | undefined; + ageRange?: + | { + min: number; + max?: number | undefined; + } + | undefined; + profileUrl?: string | undefined; + username?: string | undefined; + birthday: string; + + _raw: string; + _json: any; + } + + interface AuthenticateOptions extends passport.AuthenticateOptions { + authType?: string | undefined; + } + + interface StrategyOption { + clientID: string; + clientSecret: string; + callbackURL: string; + domain: string; + scopeSeparator?: string | undefined; + enableProof?: boolean | undefined; + profileFields?: string[] | undefined; + state?: boolean | undefined; + } + + interface StrategyOptionWithRequest extends StrategyOption { + passReqToCallback: true; + } + interface ExtraVerificationParams { + audience?: string | undefined; + connection?: string | undefined; + prompt?: string | undefined; + } + + type VerifyFunction = ( + accessToken: string, + refreshToken: string, + extraParams: ExtraVerificationParams, + profile: Profile, + done: (error: any, user?: any, info?: any) => void, + ) => void; + + type VerifyFunctionWithRequest = ( + req: express.Request, + accessToken: string, + refreshToken: string, + extraParams: ExtraVerificationParams, + profile: Profile, + done: (error: any, user?: any, info?: any) => void, + ) => void; + + export import Strategy = StrategyInternal; + } + + export = StrategyInternal; +} diff --git a/yarn.lock b/yarn.lock index 675eba1c8c..8a2dfb2568 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4283,9 +4283,11 @@ __metadata: "@backstage/plugin-auth-backend": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" "@backstage/types": "workspace:^" + "@types/passport": "npm:^1.0.3" "@types/passport-auth0": "npm:^1.0.5" "@types/passport-oauth2": "npm:^1.4.15" express: "npm:^4.22.0" + passport: "npm:^0.7.0" passport-auth0: "npm:^1.4.3" passport-oauth2: "npm:^1.6.1" supertest: "npm:^7.0.0"