1

I have followed and set up via @loopback/authentication But the authentication is added to sequence.ts for all calls. I am unable to skip authentication for Explorer component

My open source repo: opencommerce/questionnaire-server

Details:




    import {BootMixin} from '@loopback/boot';
      import {ApplicationConfig} from '@loopback/core';
      import {
        RestExplorerBindings,
        RestExplorerComponent,
      } from '@loopback/rest-explorer';
      import {RepositoryMixin} from '@loopback/repository';
      import {RestApplication} from '@loopback/rest';
      import {ServiceMixin} from '@loopback/service-proxy';
      import {
        AuthenticationComponent,
        AuthenticationBindings,
      } from '@loopback/authentication';
      import {MyAuthStrategyProvider} from './providers';
      import * as path from 'path';
      import {MySequence} from './sequence';

      export class QuestionnaireApplication extends BootMixin(
        ServiceMixin(RepositoryMixin(RestApplication)),
      ) {
        constructor(options: ApplicationConfig = {}) {
          super(options);

          // Set up the custom sequence
          this.sequence(MySequence);

          // Set up default home page
          this.static('/', path.join(__dirname, '../../public'));

          // Customize @loopback/rest-explorer configuration here
          this.bind(RestExplorerBindings.CONFIG).to({
            path: '/explorer',
          });
          this.component(RestExplorerComponent);

          this.projectRoot = __dirname;

          this.component(AuthenticationComponent);
          this.bind(AuthenticationBindings.STRATEGY).toProvider(
              MyAuthStrategyProvider,
          );

          // Customize @loopback/boot Booter Conventions here
          this.bootOptions = {
            controllers: {
              // Customize ControllerBooter Conventions here
              dirs: ['controllers'],
              extensions: ['.controller.js'],
              nested: true,
            },
          };
        }
      }




    import {inject} from '@loopback/context';
      import {
        FindRoute,
        InvokeMethod,
        ParseParams,
        Reject,
        RequestContext,
        RestBindings,
        Send,
        SequenceHandler,
      } from '@loopback/rest';
      import {AuthenticationBindings, AuthenticateFn} from '@loopback/authentication';

      const SequenceActions = RestBindings.SequenceActions;

      export class MySequence implements SequenceHandler {
        constructor(
          @inject(SequenceActions.FIND_ROUTE) protected findRoute: FindRoute,
          @inject(SequenceActions.PARSE_PARAMS) protected parseParams: ParseParams,
          @inject(SequenceActions.INVOKE_METHOD) protected invoke: InvokeMethod,
          @inject(SequenceActions.SEND) public send: Send,
          @inject(SequenceActions.REJECT) public reject: Reject,
          @inject(AuthenticationBindings.AUTH_ACTION)
          protected authenticateRequest: AuthenticateFn,
        ) {}

        async handle(context: RequestContext) {
          try {
            const {request, response} = context;
            const route = this.findRoute(request);

            // This is the important line added to the default sequence implementation
            await this.authenticateRequest(request);

            // Authentication successful, proceed to invoke controller
            const args = await this.parseParams(request, route);
            const result = await this.invoke(route, args);
            this.send(response, result);
          } catch (err) {
            this.reject(context, err);
          }
        }
      }

Error while accessing / .

Unhandled error in GET /: 500 Error: The key controller.current.ctor was not bound to any value.
at QuestionnaireApplication.getBinding (/opt/lampp7.2/htdocs/opencommerce/questionnaire-server/node_modules/@loopback/context/dist/src/context.js:225:15)
at RestServer.getBinding (/opt/lampp7.2/htdocs/opencommerce/questionnaire-server/node_modules/@loopback/context/dist/src/context.js:221:33)
at RequestContext.getBinding (/opt/lampp7.2/htdocs/opencommerce/questionnaire-server/node_modules/@loopback/context/dist/src/context.js:221:33)
at RequestContext.getValueOrPromise (/opt/lampp7.2/htdocs/opencommerce/questionnaire-server/node_modules/@loopback/context/dist/src/context.js:260:30)
at resolution_session_1.ResolutionSession.runWithInjection.s (/opt/lampp7.2/htdocs/opencommerce/questionnaire-server/node_modules/@loopback/context/dist/src/resolver.js:73:24)
at value_promise_1.tryWithFinally (/opt/lampp7.2/htdocs/opencommerce/questionnaire-server/node_modules/@loopback/context/dist/src/resolution-session.js:89:53)
at Object.tryWithFinally (/opt/lampp7.2/htdocs/opencommerce/questionnaire-server/node_modules/@loopback/context/dist/src/value-promise.js:162:18)
at Function.runWithInjection (/opt/lampp7.2/htdocs/opencommerce/questionnaire-server/node_modules/@loopback/context/dist/src/resolution-session.js:89:32)
at resolve (/opt/lampp7.2/htdocs/opencommerce/questionnaire-server/node_modules/@loopback/context/dist/src/resolver.js:66:59)
at value_promise_1.resolveList (/opt/lampp7.2/htdocs/opencommerce/questionnaire-server/node_modules/@loopback/context/dist/src/resolver.js:144:16)
at Object.resolveList (/opt/lampp7.2/htdocs/opencommerce/questionnaire-server/node_modules/@loopback/context/dist/src/value-promise.js:135:32)
at resolveInjectedArguments (/opt/lampp7.2/htdocs/opencommerce/questionnaire-server/node_modules/@loopback/context/dist/src/resolver.js:128:28)
at Object.instantiateClass (/opt/lampp7.2/htdocs/opencommerce/questionnaire-server/node_modules/@loopback/context/dist/src/resolver.js:37:27)
at Binding._getValue (/opt/lampp7.2/htdocs/opencommerce/questionnaire-server/node_modules/@loopback/context/dist/src/binding.js:338:50)
at resolution_session_1.ResolutionSession.runWithBinding.s (/opt/lampp7.2/htdocs/opencommerce/questionnaire-server/node_modules/@loopback/context/dist/src/binding.js:189:90)
at value_promise_1.tryWithFinally (/opt/lampp7.2/htdocs/opencommerce/questionnaire-server/node_modules/@loopback/context/dist/src/resolution-session.js:69:53)
Milind Singh
  • 296
  • 6
  • 23
  • 1
    Normally, you shouldn't have issues if you don't put the decorator Strategy on your endpoint. Could you post the code of your controller? – hanego Jan 02 '19 at 14:17
  • Sure I'll add the whole code today. – Milind Singh Jan 02 '19 at 15:36
  • @angelwally I have added my github repo. Its the project in generated with `lb4`. – Milind Singh Jan 02 '19 at 17:39
  • I don't see any controller with the path /. Do you have one? – hanego Jan 02 '19 at 17:46
  • https://github.com/opencommerce/questionnaire-server/blob/master/src/application.ts `this.component(RestExplorerComponent);` It handles the `/` path and renders a swagger ui for the API. But when I add the authentication as in https://github.com/opencommerce/questionnaire-server/blob/master/src/sequence.ts `await this.authenticateRequest(request);`results in exception as above. – Milind Singh Jan 02 '19 at 18:47
  • Hi @angelwally I have updated the question with complete details. Please review, – Milind Singh Jan 04 '19 at 07:00

3 Answers3

1

One possible issue you can have is that you bind the values after setting your sequence. Then the bindings does not exist when Loopback tries to resolve them. You should put something more like this:

      this.bind(RestExplorerBindings.CONFIG).to({
        path: '/explorer',
      });
      this.component(RestExplorerComponent);

      this.component(AuthenticationComponent);
      this.bind(AuthenticationBindings.STRATEGY).toProvider(
          MyAuthStrategyProvider,
      );

      this.sequence(MySequence);
hanego
  • 1,595
  • 1
  • 14
  • 27
1

Your custom sequence does not skip the authentication for static route /. It can be skipped auth as below:

if (!(route instanceof StaticAssetsRoute)) {
  // do your login stuff here
}

Now the updated sequence.ts will be:

import {inject} from '@loopback/context';
import {
  FindRoute,
  InvokeMethod,
  ParseParams,
  Reject,
  RequestContext,
  RestBindings,
  Send,
  SequenceHandler,
  StaticAssetsRoute,
} from '@loopback/rest';
import {AuthenticationBindings, AuthenticateFn} from '@loopback/authentication';

const SequenceActions = RestBindings.SequenceActions;

export class MySequence implements SequenceHandler {
  constructor(
    @inject(SequenceActions.FIND_ROUTE) protected findRoute: FindRoute,
    @inject(SequenceActions.PARSE_PARAMS) protected parseParams: ParseParams,
    @inject(SequenceActions.INVOKE_METHOD) protected invoke: InvokeMethod,
    @inject(SequenceActions.SEND) public send: Send,
    @inject(SequenceActions.REJECT) public reject: Reject,
    @inject(AuthenticationBindings.AUTH_ACTION)
    protected authenticateRequest: AuthenticateFn,
  ) {}

  async handle(context: RequestContext) {
    try {
      const {request, response} = context;
      const route = this.findRoute(request);

      // This is the important line added to the default sequence implementation
      if (!(route instanceof StaticAssetsRoute)) {
        await this.authenticateRequest(request);
      }

      // Authentication successful, proceed to invoke controller
      const args = await this.parseParams(request, route);
      const result = await this.invoke(route, args);
      this.send(response, result);
    } catch (err) {
      this.reject(context, err);
    }
  }
}
Risha Tiwari
  • 111
  • 4
1

I'm using the following to determine if the route is static.

private static isStaticRoute(route: ResolvedRoute): boolean {
  return route.path.search(/^\/explorer\/*/) === 0;
}