Merge pull request #1368 from spotify/auth-documentation

Auth docs
This commit is contained in:
Raghunandan Balachandran
2020-06-18 14:20:07 +02:00
committed by GitHub
5 changed files with 226 additions and 39 deletions
+23 -1
View File
@@ -157,25 +157,47 @@ export type ProfileInfoOptions = {
optional?: boolean;
};
/**
* This API provides access to profile information of the user from an auth provider.
*/
export type ProfileInfoApi = {
getProfile(options?: ProfileInfoOptions): Promise<ProfileInfo | undefined>;
};
/**
* Profile information of the user from an auth provider.
*/
export type ProfileInfo = {
provider: string;
/**
* Email ID.
*/
email: string;
/**
* Display name that can be presented to the user.
*/
name?: string;
/**
* URL to an avatar image of the user.
*/
picture?: string;
};
/**
* Session state values passed to subscribers of the SessionStateApi.
*/
export enum SessionState {
SignedIn = 'SignedIn',
SignedOut = 'SignedOut',
}
/**
* This API provides access to an sessionState$ observable which provides an update when the
* user performs a sign in or sign out from an auth provider.
*/
export type SessionStateApi = {
sessionState$(): Observable<SessionState>;
};
/**
* Provides authentication towards Google APIs and identities.
*
@@ -57,6 +57,8 @@ export class EnvironmentHandler implements AuthProviderRouteHandlers {
async logout(req: express.Request, res: express.Response): Promise<void> {
const provider = this.getProviderForEnv(req);
await provider.logout(req, res);
if (provider.logout) {
await provider.logout(req, res);
}
}
}
@@ -21,13 +21,14 @@ import {
RedirectInfo,
RefreshTokenResponse,
ProfileInfo,
ProviderStrategy,
} from '../providers/types';
export const makeProfileInfo = (
profile: passport.Profile,
params: any,
): ProfileInfo => {
const { provider, displayName: name } = profile;
const { displayName: name } = profile;
let email = '';
if (profile.emails) {
@@ -51,7 +52,6 @@ export const makeProfileInfo = (
}
return {
provider,
name,
email,
picture,
@@ -100,12 +100,12 @@ export const executeFrameHandlerStrategy = async (
};
export const executeRefreshTokenStrategy = async (
providerstrategy: passport.Strategy,
providerStrategy: passport.Strategy,
refreshToken: string,
scope: string,
): Promise<RefreshTokenResponse> => {
return new Promise((resolve, reject) => {
const anyStrategy = providerstrategy as any;
const anyStrategy = providerStrategy as any;
const OAuth2 = anyStrategy._oauth2.constructor;
const oauth2 = new OAuth2(
anyStrategy._oauth2._clientId,
@@ -149,12 +149,12 @@ export const executeRefreshTokenStrategy = async (
};
export const executeFetchUserProfileStrategy = async (
providerstrategy: passport.Strategy,
providerStrategy: passport.Strategy,
accessToken: string,
params: any,
): Promise<ProfileInfo> => {
return new Promise((resolve, reject) => {
const anyStrategy = providerstrategy as any;
const anyStrategy = (providerStrategy as unknown) as ProviderStrategy;
anyStrategy.userProfile(
accessToken,
(error: Error, passportProfile: passport.Profile) => {
@@ -44,7 +44,9 @@ export const createAuthProviderRouter = (
router.get('/start', provider.start.bind(provider));
router.get('/handler/frame', provider.frameHandler.bind(provider));
router.post('/handler/frame', provider.frameHandler.bind(provider));
router.post('/logout', provider.logout.bind(provider));
if (provider.logout) {
router.post('/logout', provider.logout.bind(provider));
}
if (provider.refresh) {
router.get('/refresh', provider.refresh.bind(provider));
}
+191 -30
View File
@@ -18,48 +18,163 @@ import express from 'express';
import { Logger } from 'winston';
export type OAuthProviderOptions = {
/**
* Client ID of the auth provider.
*/
clientID: string;
/**
* Client Secret of the auth provider.
*/
clientSecret: string;
/**
* Callback URL to be passed to the auth provider to redirect to after the user signs in.
*/
callbackURL: string;
};
export type SAMLProviderConfig = {
entryPoint: string;
issuer: string;
};
export type EnvironmentProviderConfig = {
[key: string]: OAuthProviderConfig | SAMLProviderConfig;
};
export type AuthProviderConfig = {
baseUrl: string;
};
export type OAuthProviderConfig = {
/**
* Cookies can be marked with a secure flag to send cookies only when the request
* is over an encrypted channel (HTTPS).
*
* For development environment we don't mark the cookie as secure since we serve
* localhost over HTTP.
*/
secure: boolean;
appOrigin: string; // http://localhost:3000
/**
* The protocol://domain[:port] where the app (frontend) is hosted. This is used to post messages back
* to the window that initiates an auth request.
*/
appOrigin: string;
/**
* Client ID of the auth provider.
*/
clientId: string;
/**
* Client Secret of the auth provider.
*/
clientSecret: string;
};
export type EnvironmentProviderConfig = {
/**
* key, values are environment names and OAuthProviderConfigs
*
* For e.g
* {
* development: DevelopmentOAuthProviderConfig
* production: ProductionOAuthProviderConfig
* }
*/
[key: string]: OAuthProviderConfig;
};
export type AuthProviderConfig = {
/**
* The protocol://domain[:port] where the app is hosted. This is used to construct the
* callbackURL to redirect to once the user signs in to the auth provider.
*/
baseUrl: string;
};
/**
* Any OAuth provider needs to implement this interface which has provider specific
* handlers for different methods to perform authentication, get access tokens,
* refresh tokens and perform sign out.
*/
export interface OAuthProviderHandlers {
/**
* This method initiates a sign in request with an auth provider.
* @param {express.Request} req
* @param options
*/
start(req: express.Request, options: any): Promise<any>;
/**
* Handles the redirect from the auth provider when the user has signed in.
* @param {express.Request} req
*/
handler(req: express.Request): Promise<any>;
/**
* (Optional) Given a refresh token and scope fetches a new access token from the auth provider.
* @param {string} refreshToken
* @param {string} scope
*/
refresh?(refreshToken: string, scope: string): Promise<any>;
/**
* (Optional) Sign out of the auth provider.
*/
logout?(): Promise<any>;
}
/**
* Any Auth provider needs to implement this interface which handles the routes in the
* auth backend. Any auth API requests from the frontend reaches these methods.
*
* The routes in the auth backend API are tied to these methods like below
*
* /auth/[provider]/start -> start
* /auth/[provider]/handler/frame -> frameHandler
* /auth/[provider]/refresh -> refresh
* /auth/[provider]/logout -> logout
*/
export interface AuthProviderRouteHandlers {
/**
* Handles the start route of the API. This initiates a sign in request with an auth provider.
*
* Request
* - scopes for the auth request (Optional)
* Response
* - redirect to the auth provider for the user to sign in or consent.
* - sets a nonce cookie and also pass the nonce as 'state' query parameter in the redirect request
*
* @param {express.Request} req
* @param {express.Response} res
*/
start(req: express.Request, res: express.Response): Promise<any>;
frameHandler(req: express.Request, res: express.Response): Promise<any>;
refresh?(req: express.Request, res: express.Response): Promise<any>;
logout(req: express.Request, res: express.Response): Promise<any>;
}
export type SAMLEnvironmentProviderConfig = {
[key: string]: SAMLProviderConfig;
};
/**
* Once the user signs in or consents in the OAuth screen, the auth provider redirects to the
* callbackURL which is handled by this method.
*
* Request
* - to contain a nonce cookie and a 'state' query parameter
* Response
* - postMessage to the window with a payload that contains accessToken, expiryInSeconds?, idToken? and scope.
* - sets a refresh token cookie if the auth provider supports refresh tokens
*
* @param {express.Request} req
* @param {express.Response} res
*/
frameHandler(req: express.Request, res: express.Response): Promise<any>;
/**
* (Optional) If the auth provider supports refresh tokens then this method handles
* requests to get a new access token.
*
* Request
* - to contain a refresh token cookie and scope (Optional) query parameter.
* Response
* - payload with accessToken, expiryInSeconds?, idToken?, scope and user profile information.
*
* @param {express.Request} req
* @param {express.Response} res
*/
refresh?(req: express.Request, res: express.Response): Promise<any>;
/**
* (Optional) Handles sign out requests
*
* Response
* - removes the refresh token cookie
*
* @param {express.Request} req
* @param {express.Response} res
*/
logout?(req: express.Request, res: express.Response): Promise<any>;
}
export type AuthProviderFactory = (
globalConfig: AuthProviderConfig,
@@ -68,27 +183,42 @@ export type AuthProviderFactory = (
) => AuthProviderRouteHandlers;
export type AuthInfoBase = {
/**
* An access token issued for the signed in user.
*/
accessToken: string;
/**
* (Optional) Id token issued for the signed in user.
*/
idToken?: string;
/**
* Expiry of the access token in seconds.
*/
expiresInSeconds?: number;
/**
* Scopes granted for the access token.
*/
scope: string;
};
export type AuthInfoWithProfile = AuthInfoBase & {
profile:
| {
provider: string;
email: string;
name?: string;
picture?: string;
}
| undefined;
/**
* Profile information of the signed in user.
*/
profile: ProfileInfo | undefined;
};
export type AuthInfoPrivate = {
/**
* A refresh token issued for the signed in user.
*/
refreshToken: string;
};
/**
* Payload sent as a post message after the auth request is complete.
* If successful then has a valid payload with Auth information else contains an error.
*/
export type AuthResponse =
| {
type: 'auth-result';
@@ -100,18 +230,49 @@ export type AuthResponse =
};
export type RedirectInfo = {
/**
* URL to redirect to
*/
url: string;
/**
* Status code to use for the redirect
*/
status?: number;
};
export type ProfileInfo = {
provider: string;
/**
* Email ID of the signed in user.
*/
email: string;
/**
* Display name that can be presented to the signed in user.
*/
name: string;
/**
* URL to an image that can be used as the display image or avatar of the
* signed in user.
*/
picture: string;
};
export type RefreshTokenResponse = {
/**
* An access token issued for the signed in user.
*/
accessToken: string;
params: any;
};
export type ProviderStrategy = {
userProfile(accessToken: string, callback: Function): void;
};
export type SAMLProviderConfig = {
entryPoint: string;
issuer: string;
};
export type SAMLEnvironmentProviderConfig = {
[key: string]: SAMLProviderConfig;
};