UNPKG

@loopback/docs

Version:

Documentation files rendered at [https://loopback.io](https://loopback.io)

278 lines (215 loc) 8.32 kB
--- lang: en title: 'Managing Custom Authentication Strategy Options' keywords: LoopBack 4.0, LoopBack 4 sidebar: lb4_sidebar permalink: /doc/en/lb4/Authentication-component-options.html --- ## Managing Custom Authentication Strategy Options This is an **optional** step. If your custom authentication strategy doesn't require special options, you can skip this section. As previously mentioned in the [Authentication Decorator](Authentication-component-decorator.md) section, a custom authentication strategy should avoid repeatedly specifying its **default** options in the **@authenticate** decorator. Instead, it should define its **default** options in one place, and only specify **overriding** options in the **@authenticate** decorator when necessary. Here are the steps for accomplishing this. ### Default authentication metadata In some cases, it's desirable to have a default authentication enforcement for methods that are not explicitly decorated with `@authenticate`. To do so, we can simply configure the authentication component with `defaultMetadata` as follows: ```ts app .configure(AuthenticationBindings.COMPONENT) .to({defaultMetadata: {strategy: 'xyz'}}); ``` If multiple strategies are used for a given method, we can configure the `failOnError` option so that a strategy can abort the authentication process by throwing an error. Otherwise, other strategies will be invoked and it only fails when none of the strategies succeeds by returning a `UserProfile` or `RedirectRoute`. ```ts app.configure(AuthenticationBindings.COMPONENT).to({failOnError: true}); ``` ### Define the Options Interface and Binding Key Define an options interface and a binding key for the default options of that specific authentication strategy. ```ts export interface AuthenticationStrategyOptions { [property: string]: any; } export namespace BasicAuthenticationStrategyBindings { export const DEFAULT_OPTIONS = BindingKey.create<AuthenticationStrategyOptions>( 'authentication.strategies.basic.defaultoptions', ); } ``` ### Bind the Default Options Bind the **default** options of the custom authentication strategy to the application `application.ts` via the `BasicAuthenticationStrategyBindings.DEFAULT_OPTIONS` binding key. In this hypothetical example, our custom authentication strategy has a **default** option of `gatherStatistics` with a value of `true`. (In a real custom authentication strategy, the number of options could be more numerous) ```ts export class MyApplication extends BootMixin( ServiceMixin(RepositoryMixin(RestApplication)), ) { constructor(options?: ApplicationConfig) { super(options); //... this.bind(BasicAuthenticationStrategyBindings.DEFAULT_OPTIONS).to({ gatherStatistics: true, }); //... } } ``` ### Override Default Options In Authentication Decorator Specify overriding options in the `@authenticate` decorator only when necessary. In this example, we only specify an **overriding** option `{gatherStatistics: false}` for the `/scareme` endpoint. We use the **default** option value for the `/whoami` endpoint. ```ts import {inject} from '@loopback/context'; import {AuthenticationBindings, authenticate} from '@loopback/authentication'; import {UserProfile, securityId} from '@loopback/security'; import {get} from '@loopback/rest'; export class WhoAmIController { constructor( @inject(SecurityBindings.USER) private userProfile: UserProfile, ) {} @authenticate('basic') @get('/whoami') whoAmI(): string { return this.userProfile[securityId]; } @authenticate('basic', {gatherStatistics: false}) @get('/scareme') scareMe(): string { return 'boo!'; } } ``` ### Update Custom Authentication Strategy to Handle Options The custom authentication strategy must be updated to handle the loading of default options, and overriding them if they have been specified in the `@authenticate` decorator. Here is the updated `BasicAuthenticationStrategy`: ```ts import { AuthenticationStrategy, TokenService, AuthenticationMetadata, AuthenticationBindings, } from '@loopback/authentication'; import {UserProfile} from '@loopback/security'; import {Getter} from '@loopback/core'; export interface Credentials { username: string; password: string; } export class BasicAuthenticationStrategy implements AuthenticationStrategy { name: string = 'basic'; // ------ ADD SNIPPET --------- @inject(BasicAuthenticationStrategyBindings.DEFAULT_OPTIONS) options: AuthenticationStrategyOptions; // ----- END OF SNIPPET ------- constructor( @inject(UserServiceBindings.USER_SERVICE) private userService: UserService, @inject.getter(AuthenticationBindings.METADATA) readonly getMetaData: Getter<AuthenticationMetadata>, ) {} async authenticate(request: Request): Promise<UserProfile | undefined> { const credentials: Credentials = this.extractCredentials(request); // ------ ADD SNIPPET --------- await this.processOptions(); if (this.options.gatherStatistics === true) { console.log(`\nGathering statistics...\n`); } else { console.log(`\nNot gathering statistics...\n`); } // ----- END OF SNIPPET ------- const user = await this.userService.verifyCredentials(credentials); const userProfile = this.userService.convertToUserProfile(user); return userProfile; } extractCredentials(request: Request): Credentials { let creds: Credentials; /** * Code to extract the 'basic' user credentials from the Authorization header */ return creds; } async processOptions() { /** Obtain the options object specified in the @authenticate decorator of a controller method associated with the current request. The AuthenticationMetadata interface contains : strategy:string, options?:object We want the options property. */ const controllerMethodAuthenticationMetadata = await this.getMetaData(); if (!this.options) this.options = {}; //if no default options were bound, assign empty options object //override default options with request-level options this.options = Object.assign( {}, this.options, controllerMethodAuthenticationMetadata.options, ); } } ``` **Inject** default options into a property `options` using the `BasicAuthenticationStrategyBindings.DEFAULT_OPTIONS` binding key. **Inject** a `getter` named `getMetaData` that returns `AuthenticationMetadata` using the `AuthenticationBindings.METADATA` binding key. This metadata contains the parameters passed into the `@authenticate` decorator. Create a function named `processOptions()` that obtains the default options, and overrides them with any request-level overriding options specified in the `@authenticate` decorator. Then, in the `authenticate()` function of the custom authentication strategy, call the `processOptions()` function, and have the custom authentication strategy react to the updated options. ## Summary We've gone through the main steps for adding `authentication` to your LoopBack 4 application. Your `application.ts` should look similar to this: ```ts import { AuthenticationComponent, registerAuthenticationStrategy, } from '@loopback/authentication'; export class MyApplication extends BootMixin( ServiceMixin(RepositoryMixin(RestApplication)), ) { constructor(options?: ApplicationConfig) { super(options); /* set up miscellaneous bindings */ //... // ------ ADD SNIPPET --------- // load the authentication component this.component(AuthenticationComponent); // register your custom authentication strategy registerAuthenticationStrategy(this, BasicAuthenticationStrategy); // use your custom authenticating sequence this.sequence(MyAuthenticatingSequence); // ------------- END OF SNIPPET ------------- this.static('/', path.join(__dirname, '../public')); this.projectRoot = __dirname; this.bootOptions = { controllers: { dirs: ['controllers'], extensions: ['.controller.js'], nested: true, }, }; } ``` You can find a **completed example** and **tutorial** of a LoopBack 4 application with JWT authentication [here](./tutorials/authentication/Authentication-Tutorial.md).