Merge pull request #2024 from spotify/rugvip/auth-config

plugins/auth-backend: refactor OAuthProvider to derive options from {app,backend}.baseUrl
This commit is contained in:
Patrik Oldsberg
2020-08-20 10:53:26 +02:00
committed by GitHub
11 changed files with 67 additions and 81 deletions
-12
View File
@@ -54,8 +54,6 @@ auth:
providers:
google:
development:
appOrigin: 'http://localhost:3000/'
secure: false
clientId:
$secret:
env: AUTH_GOOGLE_CLIENT_ID
@@ -64,8 +62,6 @@ auth:
env: AUTH_GOOGLE_CLIENT_SECRET
github:
development:
appOrigin: 'http://localhost:3000/'
secure: false
clientId:
$secret:
env: AUTH_GITHUB_CLIENT_ID
@@ -77,8 +73,6 @@ auth:
env: AUTH_GITHUB_ENTERPRISE_INSTANCE_URL
gitlab:
development:
appOrigin: 'http://localhost:3000/'
secure: false
clientId:
$secret:
env: AUTH_GITLAB_CLIENT_ID
@@ -94,8 +88,6 @@ auth:
# issuer: "passport-saml"
okta:
development:
appOrigin: 'http://localhost:3000/'
secure: false
clientId:
$secret:
env: AUTH_OKTA_CLIENT_ID
@@ -107,8 +99,6 @@ auth:
env: AUTH_OKTA_AUDIENCE
oauth2:
development:
appOrigin: 'http://localhost:3000/'
secure: false
clientId:
$secret:
env: AUTH_OAUTH2_CLIENT_ID
@@ -123,8 +113,6 @@ auth:
env: AUTH_OAUTH2_TOKEN_URL
auth0:
development:
appOrigin: 'http://localhost:3000/'
secure: false
clientId:
$secret:
env: AUTH_AUTH0_CLIENT_ID
@@ -205,8 +205,9 @@ describe('OAuthProvider', () => {
providerId: 'test-provider',
secure: false,
disableRefresh: true,
baseUrl: 'http://localhost:7000/auth',
appOrigin: 'http://localhost:3000',
cookieDomain: 'localhost',
cookiePath: '/auth/test-provider',
tokenIssuer: {
issueToken: async () => 'my-id-token',
listPublicKeys: async () => ({ keys: [] }),
+33 -17
View File
@@ -23,6 +23,7 @@ import {
WebMessageResponse,
BackstageIdentity,
OAuthState,
AuthProviderConfig,
} from '../providers/types';
import { InputError } from '@backstage/backend-common';
import { TokenIssuer } from '../identity';
@@ -35,7 +36,8 @@ export type Options = {
secure: boolean;
disableRefresh?: boolean;
persistScopes?: boolean;
baseUrl: string;
cookieDomain: string;
cookiePath: string;
appOrigin: string;
tokenIssuer: TokenIssuer;
};
@@ -120,17 +122,31 @@ export const ensuresXRequestedWith = (req: express.Request) => {
};
export class OAuthProvider implements AuthProviderRouteHandlers {
private readonly domain: string;
private readonly basePath: string;
static fromConfig(
config: AuthProviderConfig,
providerHandlers: OAuthProviderHandlers,
options: Pick<
Options,
'providerId' | 'persistScopes' | 'disableRefresh' | 'tokenIssuer'
>,
): OAuthProvider {
const { origin: appOrigin } = new URL(config.appUrl);
const secure = config.baseUrl.startsWith('https://');
const url = new URL(config.baseUrl);
const cookiePath = `${url.pathname}/${options.providerId}`;
return new OAuthProvider(providerHandlers, {
...options,
appOrigin,
cookieDomain: url.hostname,
cookiePath,
secure,
});
}
constructor(
private readonly providerHandlers: OAuthProviderHandlers,
private readonly options: Options,
) {
const url = new URL(options.baseUrl);
this.domain = url.hostname;
this.basePath = url.pathname;
}
) {}
async start(req: express.Request, res: express.Response): Promise<void> {
// retrieve scopes from request
@@ -298,8 +314,8 @@ export class OAuthProvider implements AuthProviderRouteHandlers {
maxAge: TEN_MINUTES_MS,
secure: this.options.secure,
sameSite: 'lax',
domain: this.domain,
path: `${this.basePath}/${this.options.providerId}/handler`,
domain: this.options.cookieDomain,
path: `${this.options.cookiePath}/handler`,
httpOnly: true,
});
};
@@ -309,8 +325,8 @@ export class OAuthProvider implements AuthProviderRouteHandlers {
maxAge: TEN_MINUTES_MS,
secure: this.options.secure,
sameSite: 'lax',
domain: this.domain,
path: `${this.basePath}/${this.options.providerId}/handler`,
domain: this.options.cookieDomain,
path: `${this.options.cookiePath}/handler`,
httpOnly: true,
});
};
@@ -327,8 +343,8 @@ export class OAuthProvider implements AuthProviderRouteHandlers {
maxAge: THOUSAND_DAYS_MS,
secure: this.options.secure,
sameSite: 'lax',
domain: this.domain,
path: `${this.basePath}/${this.options.providerId}`,
domain: this.options.cookieDomain,
path: this.options.cookiePath,
httpOnly: true,
});
};
@@ -336,10 +352,10 @@ export class OAuthProvider implements AuthProviderRouteHandlers {
private removeRefreshTokenCookie = (res: express.Response) => {
res.cookie(`${this.options.providerId}-refresh-token`, '', {
maxAge: 0,
secure: false,
secure: this.options.secure,
sameSite: 'lax',
domain: `${this.domain}`,
path: `${this.basePath}/${this.options.providerId}`,
domain: this.options.cookieDomain,
path: this.options.cookiePath,
httpOnly: true,
});
};
@@ -141,19 +141,17 @@ export class Auth0AuthProvider implements OAuthProviderHandlers {
}
export function createAuth0Provider(
{ baseUrl }: AuthProviderConfig,
config: AuthProviderConfig,
_: string,
envConfig: Config,
logger: Logger,
tokenIssuer: TokenIssuer,
) {
const providerId = 'auth0';
const secure = envConfig.getBoolean('secure');
const appOrigin = envConfig.getString('appOrigin');
const clientID = envConfig.getString('clientId');
const clientSecret = envConfig.getString('clientSecret');
const domain = envConfig.getString('domain');
const callbackURL = `${baseUrl}/${providerId}/handler/frame`;
const callbackURL = `${config.baseUrl}/${providerId}/handler/frame`;
const opts = {
clientID,
@@ -175,12 +173,9 @@ export function createAuth0Provider(
return undefined;
}
return new OAuthProvider(new Auth0AuthProvider(opts), {
return OAuthProvider.fromConfig(config, new Auth0AuthProvider(opts), {
disableRefresh: true,
providerId,
secure,
baseUrl,
appOrigin,
tokenIssuer,
});
}
@@ -125,15 +125,13 @@ export class GithubAuthProvider implements OAuthProviderHandlers {
}
export function createGithubProvider(
{ baseUrl }: AuthProviderConfig,
config: AuthProviderConfig,
_: string,
envConfig: Config,
logger: Logger,
tokenIssuer: TokenIssuer,
) {
const providerId = 'github';
const secure = envConfig.getBoolean('secure');
const appOrigin = envConfig.getString('appOrigin');
const clientID = envConfig.getString('clientId');
const clientSecret = envConfig.getString('clientSecret');
const enterpriseInstanceUrl = envConfig.getOptionalString(
@@ -148,7 +146,7 @@ export function createGithubProvider(
const userProfileURL = enterpriseInstanceUrl
? `${enterpriseInstanceUrl}/api/v3/user`
: undefined;
const callbackURL = `${baseUrl}/${providerId}/handler/frame`;
const callbackURL = `${config.baseUrl}/${providerId}/handler/frame`;
const opts = {
clientID,
@@ -171,13 +169,10 @@ export function createGithubProvider(
);
return undefined;
}
return new OAuthProvider(new GithubAuthProvider(opts), {
return OAuthProvider.fromConfig(config, new GithubAuthProvider(opts), {
disableRefresh: true,
persistScopes: true,
providerId,
secure,
baseUrl,
appOrigin,
tokenIssuer,
});
}
@@ -132,20 +132,18 @@ export class GitlabAuthProvider implements OAuthProviderHandlers {
}
export function createGitlabProvider(
{ baseUrl }: AuthProviderConfig,
config: AuthProviderConfig,
_: string,
envConfig: Config,
logger: Logger,
tokenIssuer: TokenIssuer,
) {
const providerId = 'gitlab';
const secure = envConfig.getBoolean('secure');
const appOrigin = envConfig.getString('appOrigin');
const clientID = envConfig.getString('clientId');
const clientSecret = envConfig.getString('clientSecret');
const audience = envConfig.getString('audience');
const baseURL = audience || 'https://gitlab.com';
const callbackURL = `${baseUrl}/${providerId}/handler/frame`;
const callbackURL = `${config.baseUrl}/${providerId}/handler/frame`;
const opts = {
clientID,
@@ -166,12 +164,9 @@ export function createGitlabProvider(
);
return undefined;
}
return new OAuthProvider(new GitlabAuthProvider(opts), {
return OAuthProvider.fromConfig(config, new GitlabAuthProvider(opts), {
disableRefresh: true,
providerId,
secure,
baseUrl,
appOrigin,
tokenIssuer,
});
}
@@ -144,18 +144,16 @@ export class GoogleAuthProvider implements OAuthProviderHandlers {
}
export function createGoogleProvider(
{ baseUrl }: AuthProviderConfig,
config: AuthProviderConfig,
_: string,
envConfig: Config,
logger: Logger,
tokenIssuer: TokenIssuer,
) {
const providerId = 'google';
const secure = envConfig.getBoolean('secure');
const appOrigin = envConfig.getString('appOrigin');
const clientID = envConfig.getString('clientId');
const clientSecret = envConfig.getString('clientSecret');
const callbackURL = `${baseUrl}/${providerId}/handler/frame`;
const callbackURL = `${config.baseUrl}/${providerId}/handler/frame`;
const opts = {
clientID,
@@ -175,12 +173,9 @@ export function createGoogleProvider(
);
return undefined;
}
return new OAuthProvider(new GoogleAuthProvider(opts), {
return OAuthProvider.fromConfig(config, new GoogleAuthProvider(opts), {
disableRefresh: false,
providerId,
secure,
baseUrl,
appOrigin,
tokenIssuer,
});
}
@@ -29,7 +29,7 @@ import {
} from '../../lib/PassportStrategyHelper';
import {
AuthProviderConfig,
GenericOAuth2ProviderOptions,
OAuthProviderOptions,
OAuthProviderHandlers,
OAuthResponse,
PassportDoneCallback,
@@ -41,10 +41,15 @@ type PrivateInfo = {
refreshToken: string;
};
export type OAuth2AuthProviderOptions = OAuthProviderOptions & {
authorizationURL: string;
tokenURL: string;
};
export class OAuth2AuthProvider implements OAuthProviderHandlers {
private readonly _strategy: OAuth2Strategy;
constructor(options: GenericOAuth2ProviderOptions) {
constructor(options: OAuth2AuthProviderOptions) {
this._strategy = new OAuth2Strategy(
{ ...options, passReqToCallback: false as true },
(
@@ -142,18 +147,16 @@ export class OAuth2AuthProvider implements OAuthProviderHandlers {
}
export function createOAuth2Provider(
{ baseUrl }: AuthProviderConfig,
config: AuthProviderConfig,
_: string,
envConfig: Config,
logger: Logger,
tokenIssuer: TokenIssuer,
) {
const providerId = 'oauth2';
const secure = envConfig.getBoolean('secure');
const appOrigin = envConfig.getString('appOrigin');
const clientID = envConfig.getString('clientId');
const clientSecret = envConfig.getString('clientSecret');
const callbackURL = `${baseUrl}/${providerId}/handler/frame`;
const callbackURL = `${config.baseUrl}/${providerId}/handler/frame`;
const authorizationURL = envConfig.getString('authorizationURL');
const tokenURL = envConfig.getString('tokenURL');
@@ -182,12 +185,9 @@ export function createOAuth2Provider(
);
return undefined;
}
return new OAuthProvider(new OAuth2AuthProvider(opts), {
return OAuthProvider.fromConfig(config, new OAuth2AuthProvider(opts), {
disableRefresh: false,
providerId,
secure,
baseUrl,
appOrigin,
tokenIssuer,
});
}
@@ -164,19 +164,17 @@ export class OktaAuthProvider implements OAuthProviderHandlers {
}
export function createOktaProvider(
{ baseUrl }: AuthProviderConfig,
config: AuthProviderConfig,
_: string,
envConfig: Config,
logger: Logger,
tokenIssuer: TokenIssuer,
) {
const providerId = 'okta';
const secure = envConfig.getBoolean('secure');
const appOrigin = envConfig.getString('appOrigin');
const clientID = envConfig.getString('clientId');
const clientSecret = envConfig.getString('clientSecret');
const audience = envConfig.getString('audience');
const callbackURL = `${baseUrl}/${providerId}/handler/frame`;
const callbackURL = `${config.baseUrl}/${providerId}/handler/frame`;
const opts = {
audience,
@@ -197,12 +195,9 @@ export function createOktaProvider(
);
return undefined;
}
return new OAuthProvider(new OktaAuthProvider(opts), {
return OAuthProvider.fromConfig(config, new OktaAuthProvider(opts), {
disableRefresh: false,
providerId,
secure,
baseUrl,
appOrigin,
tokenIssuer,
});
}
@@ -93,6 +93,11 @@ export type AuthProviderConfig = {
* callbackURL to redirect to once the user signs in to the auth provider.
*/
baseUrl: string;
/**
* The base URL of the app as provided by app.baseUrl
*/
appUrl: string;
};
/**
+2 -1
View File
@@ -36,6 +36,7 @@ export async function createRouter(
const router = Router();
const logger = options.logger.child({ plugin: 'auth' });
const appUrl = options.config.getString('app.baseUrl');
const backendUrl = options.config.getString('backend.baseUrl');
const authUrl = `${backendUrl}/auth`;
@@ -64,7 +65,7 @@ export async function createRouter(
const providerConfig = providersConfig.getConfig(providerId);
const providerRouter = createAuthProviderRouter(
providerId,
{ baseUrl: authUrl },
{ baseUrl: authUrl, appUrl },
providerConfig,
logger,
tokenIssuer,