Skip to content

feat(authentication): resolve default authentication strategy #3637

Description

@derdeka

Suggestion

Provide a lookup for a strategy name, if metadata is not provided by decorator.

Use Cases

Instead of annotating each controller or controller method, we would like to specify a default strategy name to make the application secure by default. In our user experiences, developers sometimes forget the @authenticate decorator, which makes the endpoint accidently public available.

Examples

Here is an example how we currently redefine the AuthenticationStrategyProvider:

import { AuthenticationBindings, AuthenticationMetadata, AuthenticationStrategy, AUTHENTICATION_STRATEGY_NOT_FOUND } from '@loopback/authentication';
import { BindingScope, Getter, inject } from '@loopback/context';
import { extensionPoint, extensions, Provider } from '@loopback/core';
import { RestBindings, Request } from '@loopback/rest';

/**
 * An authentication strategy provider responsible for
 * resolving an authentication strategy by name.
 *
 * It declares an extension point to which all authentication strategy
 * implementations must register themselves as extensions.
 */
@extensionPoint(
  AuthenticationBindings.AUTHENTICATION_STRATEGY_EXTENSION_POINT_NAME,
  { scope: BindingScope.TRANSIENT },
) //this needs to be transient, e.g. for request level context.
export class AuthenticationStrategyProvider implements Provider<AuthenticationStrategy | undefined> {

  noAuthUrlPattern: RegExp[] = [
    /^\/explorer/,
    /^\/openapi.json$/,
    /^\/favicon.ico$/,
  ];

  constructor(
    @extensions() private authenticationStrategies: Getter<AuthenticationStrategy[]>,
    @inject(RestBindings.Http.REQUEST) private request: Request,
    @inject(AuthenticationBindings.METADATA) private metadata?: AuthenticationMetadata,
  ) { }

  async value(): Promise<AuthenticationStrategy | undefined> {
    const { strategy: name = 'jwt' } = this.metadata || {};

    // workaround to whitelist api explorer
    if (this.noAuthUrlPattern.some(regexp => regexp.test(this.request.url))) {
      // allow unauthenticated access
      return undefined;
    }

    const strategy = await this.findAuthenticationStrategy(name);
    if (!strategy) {
      // important to throw a non-protocol-specific error here
      const error = new Error(`The strategy '${name}' is not available.`);
      Object.assign(error, {
        code: AUTHENTICATION_STRATEGY_NOT_FOUND,
      });
      throw error;
    }
    return strategy;
  }

  async findAuthenticationStrategy(name: string): Promise<AuthenticationStrategy | undefined> {
    const strategies = await this.authenticationStrategies();
    const matchingAuthStrategy = strategies.find(a => a.name === name);
    return matchingAuthStrategy;
  }
}

modified version of https://github.com/strongloop/loopback-next/blob/c944654881140119e44fe9e1db85a0f26e59d658/packages/authentication/src/providers/auth-strategy.provider.ts#L36-L41

Acceptance criteria

TBD - will be filled by the team.

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions