From fed39a1fa7444fd997560014dc962958e52c05bb Mon Sep 17 00:00:00 2001 From: Jaime Mendes Date: Tue, 14 Jul 2020 00:02:43 +0100 Subject: [PATCH] Adds Auth0 as IdP --- packages/app/src/App.tsx | 2 +- packages/app/src/apis.ts | 11 + .../core-api/src/apis/definitions/auth.ts | 11 + .../auth/auth0/Auth0Auth.test.ts | 36 ++++ .../implementations/auth/auth0/Auth0Auth.ts | 154 ++++++++++++++ .../apis/implementations/auth/auth0/index.ts | 18 ++ .../apis/implementations/auth/auth0/types.ts | 28 +++ .../src/apis/implementations/auth/index.ts | 1 + .../core/src/layout/Sidebar/UserSettings.tsx | 6 + .../src/layout/SignInPage/auth0Provider.tsx | 89 ++++++++ .../core/src/layout/SignInPage/providers.tsx | 5 +- packages/dev-utils/src/devApp/apiFactories.ts | 13 ++ packages/storybook/.storybook/apis.js | 11 + plugins/auth-backend/README.md | 8 + .../auth-backend/src/providers/auth0/index.ts | 17 ++ .../src/providers/auth0/provider.ts | 200 ++++++++++++++++++ .../src/providers/auth0/strategy.ts | 37 ++++ .../auth-backend/src/providers/factories.ts | 2 + plugins/auth-backend/src/service/router.ts | 9 + 19 files changed, 656 insertions(+), 2 deletions(-) create mode 100644 packages/core-api/src/apis/implementations/auth/auth0/Auth0Auth.test.ts create mode 100644 packages/core-api/src/apis/implementations/auth/auth0/Auth0Auth.ts create mode 100644 packages/core-api/src/apis/implementations/auth/auth0/index.ts create mode 100644 packages/core-api/src/apis/implementations/auth/auth0/types.ts create mode 100644 packages/core/src/layout/SignInPage/auth0Provider.tsx create mode 100644 plugins/auth-backend/src/providers/auth0/index.ts create mode 100644 plugins/auth-backend/src/providers/auth0/provider.ts create mode 100644 plugins/auth-backend/src/providers/auth0/strategy.ts diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index c569308fc7..33c56796f5 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -33,7 +33,7 @@ const app = createApp({ SignInPage: props => ( ), }, diff --git a/packages/app/src/apis.ts b/packages/app/src/apis.ts index cac3d6e171..5bb17fd9c8 100644 --- a/packages/app/src/apis.ts +++ b/packages/app/src/apis.ts @@ -29,6 +29,7 @@ import { OAuth2, OktaAuth, GitlabAuth, + Auth0Auth, oauthRequestApiRef, OAuthRequestManager, googleAuthApiRef, @@ -36,6 +37,7 @@ import { oauth2ApiRef, oktaAuthApiRef, gitlabAuthApiRef, + auth0AuthApiRef, storageApiRef, WebStorage, } from '@backstage/core'; @@ -129,6 +131,15 @@ export const apis = (config: ConfigApi) => { oauthRequestApi, }), ); + + builder.add( + auth0AuthApiRef, + Auth0Auth.create({ + apiOrigin: backendUrl, + basePath: '/auth/', + oauthRequestApi, + }), + ); builder.add( oauth2ApiRef, diff --git a/packages/core-api/src/apis/definitions/auth.ts b/packages/core-api/src/apis/definitions/auth.ts index ec7aba81c4..05c4ed48af 100644 --- a/packages/core-api/src/apis/definitions/auth.ts +++ b/packages/core-api/src/apis/definitions/auth.ts @@ -264,6 +264,17 @@ export const gitlabAuthApiRef = createApiRef< description: 'Provides authentication towards Gitlab APIs', }); +/** + * Provides authentication towards Auth0 APIs. + * + * See https://auth0.com/docs/scopes/current/oidc-scopes + * for a full list of supported scopes. + */ +export const auth0AuthApiRef = createApiRef({ + id: 'core.auth.auth0', + description: 'Provides authentication towards Auth0 APIs', +}); + /** * Provides authentication for custom identity providers. */ diff --git a/packages/core-api/src/apis/implementations/auth/auth0/Auth0Auth.test.ts b/packages/core-api/src/apis/implementations/auth/auth0/Auth0Auth.test.ts new file mode 100644 index 0000000000..5912c2a364 --- /dev/null +++ b/packages/core-api/src/apis/implementations/auth/auth0/Auth0Auth.test.ts @@ -0,0 +1,36 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ + +import Auth0Auth from './Auth0Auth'; + +describe('Auth0Auth', () => { + it('should normalize scope', () => { + const tests = [ + { + arguments: ['read_user api write_repository'], + expect: new Set(['read_user', 'api', 'write_repository']), + }, + { + arguments: ['read_repository sudo'], + expect: new Set(['read_repository', 'sudo']), + }, + ]; + + for (const test of tests) { + expect(Auth0Auth.normalizeScopes(...test.arguments)).toEqual(test.expect); + } + }); +}); diff --git a/packages/core-api/src/apis/implementations/auth/auth0/Auth0Auth.ts b/packages/core-api/src/apis/implementations/auth/auth0/Auth0Auth.ts new file mode 100644 index 0000000000..3d8a42a4cc --- /dev/null +++ b/packages/core-api/src/apis/implementations/auth/auth0/Auth0Auth.ts @@ -0,0 +1,154 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ + +import Auth0Icon from '@material-ui/icons/AcUnit'; +import { DefaultAuthConnector } from '../../../../lib/AuthConnector'; +import { Auth0Session } from './types'; +import { + OpenIdConnectApi, + ProfileInfoApi, + ProfileInfo, + SessionStateApi, + SessionState, + BackstageIdentityApi, + AuthRequestOptions, + BackstageIdentity, +} from '../../../definitions/auth'; +import { OAuthRequestApi, AuthProvider } from '../../../definitions'; +import { SessionManager } from '../../../../lib/AuthSessionManager/types'; +import { RefreshingAuthSessionManager } from '../../../../lib/AuthSessionManager'; +import { Observable } from '../../../../types'; + +type CreateOptions = { + // TODO(Following the words of Rugvip): These two should be grabbed from global config when available, they're not unique to Auth0Auth + apiOrigin: string; + basePath: string; + + oauthRequestApi: OAuthRequestApi; + + environment?: string; + provider?: AuthProvider & { id: string }; +}; + +export type Auth0AuthResponse = { + providerInfo: { + accessToken: string; + idToken: string; + scope: string; + expiresInSeconds: number; + }; + profile: ProfileInfo; + backstageIdentity: BackstageIdentity; +}; + +const DEFAULT_PROVIDER = { + id: 'auth0', + title: 'Auth0', + icon: Auth0Icon, +}; + +class Auth0Auth + implements + OpenIdConnectApi, + ProfileInfoApi, + BackstageIdentityApi, + SessionStateApi { + static create({ + apiOrigin, + basePath, + environment = 'development', + provider = DEFAULT_PROVIDER, + oauthRequestApi, + }: CreateOptions) { + const connector = new DefaultAuthConnector({ + apiOrigin, + basePath, + environment, + provider, + oauthRequestApi: oauthRequestApi, + sessionTransform(res: Auth0AuthResponse): Auth0Session { + return { + ...res, + providerInfo: { + idToken: res.providerInfo.idToken, + accessToken: res.providerInfo.accessToken, + scopes: Auth0Auth.normalizeScopes(res.providerInfo.scope), + expiresAt: new Date( + Date.now() + res.providerInfo.expiresInSeconds * 1000, + ), + }, + }; + }, + }); + + const sessionManager = new RefreshingAuthSessionManager({ + connector, + defaultScopes: new Set([ + 'openid', + `email`, + `profile`, + ]), + sessionScopes: (session: Auth0Session) => session.providerInfo.scopes, + sessionShouldRefresh: (session: Auth0Session) => { + const expiresInSec = + (session.providerInfo.expiresAt.getTime() - Date.now()) / 1000; + return expiresInSec < 60 * 5; + }, + }); + + return new Auth0Auth(sessionManager); + } + + sessionState$(): Observable { + return this.sessionManager.sessionState$(); + } + + constructor(private readonly sessionManager: SessionManager) {} + + async getIdToken(options: AuthRequestOptions = {}) { + const session = await this.sessionManager.getSession(options); + return session?.providerInfo.idToken ?? ''; + } + + async logout() { + await this.sessionManager.removeSession(); + } + + async getBackstageIdentity( + options: AuthRequestOptions = {}, + ): Promise { + const session = await this.sessionManager.getSession(options); + return session?.backstageIdentity; + } + + async getProfile(options: AuthRequestOptions = {}) { + const session = await this.sessionManager.getSession(options); + return session?.profile; + } + + static normalizeScopes(scope?: string | string[]): Set { + if (!scope) { + return new Set(); + } + + const scopeList = Array.isArray(scope) + ? scope + : scope.split(/[\s|,]/).filter(Boolean); + + return new Set(scopeList); + } +} +export default Auth0Auth; diff --git a/packages/core-api/src/apis/implementations/auth/auth0/index.ts b/packages/core-api/src/apis/implementations/auth/auth0/index.ts new file mode 100644 index 0000000000..fb5d432642 --- /dev/null +++ b/packages/core-api/src/apis/implementations/auth/auth0/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ + +export * from './types'; +export { default as Auth0Auth } from './Auth0Auth'; diff --git a/packages/core-api/src/apis/implementations/auth/auth0/types.ts b/packages/core-api/src/apis/implementations/auth/auth0/types.ts new file mode 100644 index 0000000000..203ccd9fd9 --- /dev/null +++ b/packages/core-api/src/apis/implementations/auth/auth0/types.ts @@ -0,0 +1,28 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ + +import { ProfileInfo, BackstageIdentity } from '../../../definitions'; + +export type Auth0Session = { + providerInfo: { + idToken: string; + accessToken: string; + scopes: Set; + expiresAt: Date; + }; + profile: ProfileInfo; + backstageIdentity: BackstageIdentity; +}; diff --git a/packages/core-api/src/apis/implementations/auth/index.ts b/packages/core-api/src/apis/implementations/auth/index.ts index 786e9fa771..ce6e0d8570 100644 --- a/packages/core-api/src/apis/implementations/auth/index.ts +++ b/packages/core-api/src/apis/implementations/auth/index.ts @@ -19,3 +19,4 @@ export * from './gitlab'; export * from './google'; export * from './oauth2'; export * from './okta'; +export * from './auth0'; diff --git a/packages/core/src/layout/Sidebar/UserSettings.tsx b/packages/core/src/layout/Sidebar/UserSettings.tsx index 5f66fc6558..89dd9f5a39 100644 --- a/packages/core/src/layout/Sidebar/UserSettings.tsx +++ b/packages/core/src/layout/Sidebar/UserSettings.tsx @@ -21,6 +21,7 @@ import { identityApiRef, oauth2ApiRef, oktaAuthApiRef, + auth0AuthApiRef, useApi, } from '@backstage/core-api'; import Collapse from '@material-ui/core/Collapse'; @@ -69,6 +70,11 @@ export function SidebarUserSettings() { apiRef={oktaAuthApiRef} icon={Star} /> + { + const auth0AuthApi = useApi(auth0AuthApiRef); + const errorApi = useApi(errorApiRef); + + const handleLogin = async () => { + try { + const identity = await auth0AuthApi.getBackstageIdentity({ + instantPopup: true, + }); + + const profile = await auth0AuthApi.getProfile(); + + onResult({ + userId: identity!.id, + profile: profile!, + getIdToken: () => + auth0AuthApi.getBackstageIdentity().then(i => i!.idToken), + logout: async () => { + await auth0AuthApi.logout(); + }, + }); + } catch (error) { + errorApi.post(error); + } + }; + + return ( + + + Sign In + + } + > + Sign In using Auth0 + + + ); +}; + +const loader: ProviderLoader = async apis => { + const auth0AuthApi = apis.get(auth0AuthApiRef)!; + + const identity = await auth0AuthApi.getBackstageIdentity({ + optional: true, + }); + + if (!identity) { + return undefined; + } + + const profile = await auth0AuthApi.getProfile(); + + return { + userId: identity.id, + profile: profile!, + getIdToken: () => + auth0AuthApi.getBackstageIdentity().then(i => i!.idToken), + logout: async () => { + await auth0AuthApi.logout(); + }, + }; +}; + +export const auth0Provider: SignInProvider = { Component, loader }; diff --git a/packages/core/src/layout/SignInPage/providers.tsx b/packages/core/src/layout/SignInPage/providers.tsx index e4f4aee8c7..4eb3fe9222 100644 --- a/packages/core/src/layout/SignInPage/providers.tsx +++ b/packages/core/src/layout/SignInPage/providers.tsx @@ -21,6 +21,7 @@ import { customProvider } from './customProvider'; import { gitlabProvider } from './gitlabProvider'; import { oktaProvider } from './oktaProvider'; import { githubProvider } from './githubProvider'; +import { auth0Provider } from './auth0Provider'; import { SignInPageProps, SignInResult, @@ -39,7 +40,8 @@ export type SignInProviderId = | 'gitlab' | 'custom' | 'okta' - | 'github'; + | 'github' + | 'auth0'; const signInProviders: { [id in SignInProviderId]: SignInProvider } = { guest: guestProvider, @@ -48,6 +50,7 @@ const signInProviders: { [id in SignInProviderId]: SignInProvider } = { custom: customProvider, okta: oktaProvider, github: githubProvider, + auth0: auth0Provider, }; export const useSignInProviders = ( diff --git a/packages/dev-utils/src/devApp/apiFactories.ts b/packages/dev-utils/src/devApp/apiFactories.ts index 35295b4631..b88611f062 100644 --- a/packages/dev-utils/src/devApp/apiFactories.ts +++ b/packages/dev-utils/src/devApp/apiFactories.ts @@ -30,6 +30,8 @@ import { githubAuthApiRef, GitlabAuth, gitlabAuthApiRef, + Auth0Auth, + auth0AuthApiRef, } from '@backstage/core'; // TODO(rugvip): We should likely figure out how to reuse all of these between apps @@ -88,3 +90,14 @@ export const gitlabAuthApiFactory = createApiFactory({ oauthRequestApi, }), }); + +export const auth0AuthApiFactory = createApiFactory({ + implements: auth0AuthApiRef, + deps: { oauthRequestApi: oauthRequestApiRef }, + factory: ({ oauthRequestApi }) => + Auth0Auth.create({ + apiOrigin: 'http://localhost:7000', + basePath: '/auth/', + oauthRequestApi, + }), +}); diff --git a/packages/storybook/.storybook/apis.js b/packages/storybook/.storybook/apis.js index f4b7557b89..ab461faabf 100644 --- a/packages/storybook/.storybook/apis.js +++ b/packages/storybook/.storybook/apis.js @@ -18,6 +18,8 @@ import { OAuthRequestManager, OktaAuth, oktaAuthApiRef, + Auth0Auth, + auth0AuthApiRef, } from '@backstage/core'; const builder = ApiRegistry.builder(); @@ -74,6 +76,15 @@ builder.add( }), ); +builder.add( + auth0AuthApiRef, + Auth0Auth.create({ + apiOrigin: 'http://localhost:7000', + basePath: '/auth/', + oauthRequestApi, + }), +); + builder.add( oauth2ApiRef, OAuth2.create({ diff --git a/plugins/auth-backend/README.md b/plugins/auth-backend/README.md index ff0b1c7a3d..c4db98be94 100644 --- a/plugins/auth-backend/README.md +++ b/plugins/auth-backend/README.md @@ -48,6 +48,14 @@ export AUTH_OKTA_CLIENT_ID=x export AUTH_OKTA_CLIENT_SECRET=x ``` +### Auth0 + +```bash +export AUTH_AUTH0_DOMAIN=x +export AUTH_AUTH0_CLIENT_ID=x +export AUTH_AUTH0_CLIENT_SECRET=x +``` + ### SAML To try out SAML, you can use the mock identity provider: diff --git a/plugins/auth-backend/src/providers/auth0/index.ts b/plugins/auth-backend/src/providers/auth0/index.ts new file mode 100644 index 0000000000..87c9aceaa4 --- /dev/null +++ b/plugins/auth-backend/src/providers/auth0/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ + +export { createAuth0Provider } from './provider'; diff --git a/plugins/auth-backend/src/providers/auth0/provider.ts b/plugins/auth-backend/src/providers/auth0/provider.ts new file mode 100644 index 0000000000..8eebd5f158 --- /dev/null +++ b/plugins/auth-backend/src/providers/auth0/provider.ts @@ -0,0 +1,200 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ + +import express from 'express'; +import passport from 'passport'; +import Auth0Strategy, { Auth0StrategyOptionsWithRequest } from './strategy'; +import { Logger } from 'winston'; +import { TokenIssuer } from '../../identity'; +import { + EnvironmentHandler, + EnvironmentHandlers, +} from '../../lib/EnvironmentHandler'; +import { OAuthProvider } from '../../lib/OAuthProvider'; +import { + executeFetchUserProfileStrategy, + executeFrameHandlerStrategy, + executeRedirectStrategy, + executeRefreshTokenStrategy, + makeProfileInfo, +} from '../../lib/PassportStrategyHelper'; +import { + AuthProviderConfig, + EnvironmentProviderConfig, + OAuthProviderHandlers, + OAuthResponse, + PassportDoneCallback, + RedirectInfo, +} from '../types'; + +interface Auth0ProviderConfig { + clientId: string; + clientSecret: string; + domain: string; + callbackURL: string; + secure: boolean; + appOrigin: string; +} + +type PrivateInfo = { + refreshToken: string; +}; + +export class Auth0AuthProvider implements OAuthProviderHandlers { + private readonly _strategy: Auth0Strategy; + + constructor(options: Auth0StrategyOptionsWithRequest) { + this._strategy = new Auth0Strategy( + options, + ( + accessToken: any, + refreshToken: any, + params: any, + rawProfile: passport.Profile, + done: PassportDoneCallback, + ) => { + const profile = makeProfileInfo(rawProfile, params.id_token); + done( + undefined, + { + providerInfo: { + idToken: params.id_token, + accessToken, + scope: params.scope, + expiresInSeconds: params.expires_in, + }, + profile, + }, + { + refreshToken, + }, + ); + }, + ); + } + + async start( + req: express.Request, + options: Record, + ): Promise { + const providerOptions = { + ...options, + accessType: 'offline', + prompt: 'consent', + }; + return await executeRedirectStrategy(req, this._strategy, providerOptions); + } + + async handler( + req: express.Request, + ): Promise<{ response: OAuthResponse; refreshToken: string }> { + const { response, privateInfo } = await executeFrameHandlerStrategy< + OAuthResponse, + PrivateInfo + >(req, this._strategy); + + return { + response: await this.populateIdentity(response), + refreshToken: privateInfo.refreshToken, + }; + } + + async refresh(refreshToken: string, scope: string): Promise { + const { accessToken, params } = await executeRefreshTokenStrategy( + this._strategy, + refreshToken, + scope, + ); + + const profile = await executeFetchUserProfileStrategy( + this._strategy, + accessToken, + params.id_token, + ); + + return this.populateIdentity({ + providerInfo: { + accessToken, + idToken: params.id_token, + expiresInSeconds: params.expires_in, + scope: params.scope, + }, + profile, + }); + } + + // Use this function to grab the user profile info from the token + // Then populate the profile with it + private async populateIdentity( + response: OAuthResponse, + ): Promise { + const { profile } = response; + + if (!profile.email) { + throw new Error('Profile does not contain a profile'); + } + + const id = profile.email.split('@')[0]; + + return { ...response, backstageIdentity: { id } }; + } +} + +export function createAuth0Provider( + { baseUrl }: AuthProviderConfig, + providerConfig: EnvironmentProviderConfig, + logger: Logger, + tokenIssuer: TokenIssuer, +) { + const envProviders: EnvironmentHandlers = {}; + const providerId = 'auth0'; + + for (const [env, envConfig] of Object.entries(providerConfig)) { + const config = (envConfig as unknown) as Auth0ProviderConfig; + const opts = { + clientID: config.clientId, + clientSecret: config.clientSecret, + callbackURL: `${baseUrl}/${providerId}/handler/frame?env=${env}`, + domain: config.domain, + } as Auth0StrategyOptionsWithRequest; + + if (!opts.clientID || !opts.clientSecret || !opts.domain) { + if (process.env.NODE_ENV !== 'development') { + throw new Error( + 'Failed to initialize OAuth2 auth provider, set AUTH_AUTH0_CLIENT_ID, AUTH_AUTH0_CLIENT_SECRET, and AUTH_AUTH0_DOMAIN env vars', + ); + } + + logger.warn( + 'OAuth2 auth provider disabled, set AUTH_AUTH0_CLIENT_ID, AUTH_AUTH0_CLIENT_SECRET, and AUTH_AUTH0_DOMAIN env vars to enable', + ); + continue; + } + + const { secure, appOrigin } = config; + envProviders[env] = new OAuthProvider(new Auth0AuthProvider(opts), { + disableRefresh: true, + persistScopes: true, + providerId: providerId, + secure, + baseUrl, + appOrigin, + tokenIssuer, + }); + } + + return new EnvironmentHandler(providerId, envProviders); +} diff --git a/plugins/auth-backend/src/providers/auth0/strategy.ts b/plugins/auth-backend/src/providers/auth0/strategy.ts new file mode 100644 index 0000000000..4dd7440448 --- /dev/null +++ b/plugins/auth-backend/src/providers/auth0/strategy.ts @@ -0,0 +1,37 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ +import OAuth2Strategy from 'passport-oauth2'; + +export interface Auth0StrategyOptionsWithRequest { + clientID: string; + clientSecret: string; + callbackURL: string; + domain: string; + passReqToCallback: true; +} + +export default class Auth0Strategy extends OAuth2Strategy { + constructor(options: Auth0StrategyOptionsWithRequest, verify: OAuth2Strategy.VerifyFunctionWithRequest) { + const optionsWithURLs = { + ...options, + authorizationURL: `https://${options.domain}/authorize`, + tokenURL: `https://${options.domain}/oauth/token`, + userInfoURL: `https://${options.domain}/userinfo`, + apiUrl: `https://${options.domain}/api`, + } + super(optionsWithURLs, verify) + } +} diff --git a/plugins/auth-backend/src/providers/factories.ts b/plugins/auth-backend/src/providers/factories.ts index 4c9caf214b..77e5356721 100644 --- a/plugins/auth-backend/src/providers/factories.ts +++ b/plugins/auth-backend/src/providers/factories.ts @@ -23,6 +23,7 @@ import { createGoogleProvider } from './google'; import { createOAuth2Provider } from './oauth2'; import { createOktaProvider } from './okta'; import { createSamlProvider } from './saml'; +import { createAuth0Provider } from './auth0' import { AuthProviderConfig, AuthProviderFactory } from './types'; const factories: { [providerId: string]: AuthProviderFactory } = { @@ -31,6 +32,7 @@ const factories: { [providerId: string]: AuthProviderFactory } = { gitlab: createGitlabProvider, saml: createSamlProvider, okta: createOktaProvider, + auth0: createAuth0Provider, oauth2: createOAuth2Provider, }; diff --git a/plugins/auth-backend/src/service/router.ts b/plugins/auth-backend/src/service/router.ts index 6b296cba2d..256545219d 100644 --- a/plugins/auth-backend/src/service/router.ts +++ b/plugins/auth-backend/src/service/router.ts @@ -113,6 +113,15 @@ export async function createRouter( tokenURL: process.env.AUTH_OAUTH2_TOKEN_URL!, }, }, + auth0: { + development: { + appOrigin, + secure: false, + domain: process.env.AUTH_AUTH0_DOMAIN!, + clientId: process.env.AUTH_AUTH0_CLIENT_ID!, + clientSecret: process.env.AUTH_AUTH0_CLIENT_SECRET! + }, + }, }, }, };