# Adding authentication providers ## Passport We chose [Passport](http://www.passportjs.org/) as our authentication platform due to its comprehensive set of supported authentication [strategies](http://www.passportjs.org/packages/). ## How to add a new strategy provider ### Quick guide [1.](#installing-the-dependencies) Install the passport-js based provider package. [2.](#create-implementation) Create a new folder structure for the provider. [3.](#adding-an-oauth-based-provider) Implement the provider, extending the suitable framework if needed. [4.](#hook-it-up-to-the-backend) Add the provider to the backend. ### Installing the dependencies: ```bash cd plugins/auth-backend yarn add passport-provider-a yarn add @types/passport-provider-a ``` ### Create implementation Make a new folder with the name of the provider following the below file structure: ```bash plugins/auth-backend/src/providers/providerA ├── index.ts └── provider.ts ``` **`plugins/auth-backend/src/providers/providerA/provider.ts`** defines the provider class which implements a handler for the chosen framework. #### Adding an OAuth based provider If we're adding an `OAuth` based provider we would implement the [OAuthProviderHandlers](#OAuthProviderHandlers) interface. The provider class takes the provider's configuration as a class parameter. It also imports the `Strategy` from the passport package. ```ts import { Strategy as ProviderAStrategy } from 'passport-provider-a'; export class ProviderAAuthProvider implements OAuthProviderHandlers { private readonly providerConfig: AuthProviderConfig; private readonly _strategy: ProviderAStrategy; constructor(providerConfig: AuthProviderConfig) { this.providerConfig = providerConfig; this._strategy = new ProviderAStrategy( { ...providerConfig.options }, verifyFunction, // See the "Verify Callback" section ); } async start() {} async handler() {} } ``` #### Adding an non-OAuth based provider _**Note**: We have prioritized OAuth-based providers and non-OAuth providers should be considered experimental._ An non-`OAuth` based provider could implement [AuthProviderRouteHandlers](#AuthProviderRouteHandlers) instead. ```ts export class ProviderAAuthProvider implements AuthProviderRouteHandlers { private readonly providerConfig: AuthProviderConfig; private readonly _strategy: ProviderAStrategy; constructor(providerConfig: AuthProviderConfig) { this.providerConfig = providerConfig; this._strategy = new ProviderAStrategy( { ...providerConfig.options }, verifyFunction, // See the "Verify Callback" section ); } async start() {} async frameHandler() {} async logout() {} async refresh() {} // If supported } ``` #### Create method Each provider exports a create method that creates the provider instance, optionally extending a supported authorization framework. This method exists to allow for flexibility if additional frameworks are supported in the future. Implementing OAuth by returning an instance of `OAuthProvider` based of the provider's class: ```ts export function createProviderAProvider(config: AuthProviderConfig) { const provider = new ProviderAAuthProvider(config); const oauthProvider = new OAuthProvider(provider, config.provider, true); return oauthProvider; } ``` Not extending with OAuth, the main difference here is that the create method is returning a instance of the class without adding the OAuth authorization framework to it. ```ts export function createProviderAProvider(config: AuthProviderConfig) { return new ProviderAAuthProvider(config); } ``` #### Verify Callback > Strategies require what is known as a verify callback. The purpose of a verify > callback is to find the user that possesses a set of credentials. When > Passport authenticates a request, it parses the credentials contained in the > request. It then invokes the verify callback with those credentials as > arguments [...]. If the credentials are valid, the verify callback invokes > done to supply Passport with the user that authenticated. > > If the credentials are not valid (for example, if the password is incorrect), > done should be invoked with false instead of a user to indicate an > authentication failure. > > http://www.passportjs.org/docs/configure/ **`plugins/auth-backend/src/providers/providerA/index.ts`** is simply re-exporting the create method to be used for hooking the provider up to the backend. ```ts export { createProviderAProvider } from './provider'; ``` ### Hook it up to the backend **`plugins/auth-backend/src/providers/config.ts`** The provider needs to be configured properly so you need to add it to the list of configured providers, all of which implement [AuthProviderConfig](#AuthProviderConfig): ```ts export const providers = [ { provider: 'providerA', # used as an identifier options: { ... }, # consult the provider documentation for which options you should provide disableRefresh: true # if the provider lacks refresh tokens }, ``` **`plugins/auth-backend/src/providers/factories.ts`** When the `auth-backend` starts it sets up routing for all the available providers by calling `createAuthProviderRouter` on each provider. You need to import the create method from the provider and add it to the factory: ```ts import { createProviderAProvider } from './providerA'; const factories: { [providerId: string]: AuthProviderFactory } = { providerA: createProviderAProvider, }; ``` By doing this `auth-backend` automatically adds these endpoints: ```ts router.get('/auth/providerA/start'); router.get('/auth/providerA/handler/frame'); router.post('/auth/providerA/handler/frame'); router.post('/auth/providerA/logout'); router.get('/auth/providerA/refresh'); // if supported ``` As you can see each endpoint is prefixed with both `/auth` and its provider name. ### Test the new provider You can `curl -i localhost:7000/auth/providerA/start` and which should provide a `302` redirect with a `Location` header. Paste the url from that header into a web browser and you should be able to trigger the authorization flow. --- ##### OAuthProviderHandlers ```ts export interface OAuthProviderHandlers { start(req: express.Request, options: any): Promise; handler(req: express.Request): Promise; refresh?(refreshToken: string, scope: string): Promise; logout?(): Promise; } ``` ##### AuthProviderRouteHandlers ```ts export interface AuthProviderRouteHandlers { start(req: express.Request, res: express.Response): Promise; frameHandler(req: express.Request, res: express.Response): Promise; refresh?(req: express.Request, res: express.Response): Promise; logout(req: express.Request, res: express.Response): Promise; } ``` ##### AuthProviderConfig ```ts export type AuthProviderConfig = { provider: string; options: any; disableRefresh?: boolean; }; ```