feat: Add organization option to auth0 auth provider

Signed-off-by: Jack Palmer <jackpalmer@spotify.com>
This commit is contained in:
Jack Palmer
2026-01-23 10:46:51 +00:00
parent 24eb7d7933
commit 6539d9a15c
6 changed files with 123 additions and 0 deletions
@@ -32,6 +32,7 @@ export interface Config {
audience?: string;
connection?: string;
connectionScope?: string;
organization?: string;
sessionDuration?: HumanDuration | string;
};
};
@@ -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"
@@ -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,
@@ -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<string, any>): Record<string, any> {
const params = super.authorizationParams(options);
if (this.organization) {
return {
...params,
organization: this.organization,
};
}
return params;
}
}
@@ -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<string, any>): Record<string, any>;
}
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;
}
+2
View File
@@ -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"