loopbackio / loopback-next

LoopBack makes it easy to build modern API applications that require complex integrations.
https://loopback.io
Other
4.95k stars 1.07k forks source link

Only one strategy allowed in Authentication #2139

Closed sertal70 closed 5 years ago

sertal70 commented 5 years ago

Description / Steps to reproduce / Feature proposal

Hi, I'm new to loopback, before opening this issue I tried to find a solution in the issues list or in the google discussion but without success, so here I am.

I'm trying to implement a basic use case for authentication. My scenario is:

  1. client performs a call to /login endpoint passing username and password via a basic http authentication
  2. /login endpoint is decorated with @authenticate('BasicStrategy') so credentials are checked and, if they are ok, the endpoint creates and returns a JWT to the client
  3. client performs a call to /todos/count endpoint sending the JWT in the Auth header (bearer token)
  4. /todos/countis decorated with @authenticate('JwtStrategy') so the JWT token is verified and, if it is ok, the endpoint give back the result to the client

Current Behavior

Everything goes fine until step 4. where I get the error:

Unhandled error in GET /todos/count: 500 The strategy JwtStrategy is not available.

The root cause is in application.ts, where I bind the two strategies for authentication:

    this.bind(AuthenticationBindings.STRATEGY).toProvider(
      JwtAuthStrategyProvider,
    );
    this.bind(AuthenticationBindings.STRATEGY).toProvider(
      BasicHTTPAuthStrategyProvider);

and, as it is clearly stated in the documentation, only one bind is allowed for the same key.

So it seems that the current design of Authentication would not allow more than one strategy at the same time.

For this simple use case it would not be a problem (it is as simple as removing BasicStrategy, because it is used in /login endpoint only and it could be implemented in many different ways).

But what about real production scenarios where multiple strategies should be supported?

Expected Behaviour

In a real, complex project, more than one authentication strategy is often a requirement so IMO it should be supported.

dhmlau commented 5 years ago

@jannyHou , could you please take a look? I think it's related to the authentication work that you're doing. Thanks.

rohit-khanna commented 5 years ago

Hello @sertal70 ,

I am newbie into LB4 , so apologies if I am talking out of senses ( hehe ).

the below solution could be a workaround, i guess ( i have not tried though),

Let there be a CommonAuthStrategyProviderClass. Assign this Class to the AuthenticationBindings.STRATEGY Key, and then use this:

if (name === 'BasicStrategy') {
      let self = this;
      return new BasicStrategy(this.basicVerify.bind(self));
    } else if (name === 'JWTStrategy') {
      return new JWTStrategy({
        secretOrKey: this.configuration.secrets.jwtSecret,
        jwtFromRequest: ExtractJwt.fromHeader('SOME_HEADER_NAME') // Here extract from headers
      }, this.verifyCallback);
    }

Or Call two separate Auth-Component classes from each of 'if' statements ?

My thought process was: We donot specifiy the Exact Strategy in AuthProviderClass declaration and only create instance when we are sure of what kind of strategy has been decorated on the controller. So insead of exposing multiple auth provider controllers(each specific to one strategy), we can have our MainAuthController which can route/decide on the response.

Please correct me If i misunderstood something.

sertal70 commented 5 years ago

Hello @rohit-khanna, thanks for your suggestion and... welcome to the team of LB4 newbie! :)

Theoretically it should work as workaround, I'll give it a try (as soon as I have time) to verify that works and, more important, that there are no side-effects.

rohit-khanna commented 5 years ago

@sertal70 , glad to be welcomed in the growing team :) I gave the above solution a shot and it did worked.

I had two endpoints in my controller , one with 'Basic Strategy' and Other one with 'JwtStrategy'. And with the above code i was able to achieve this.

And yes, whenever you have time, you can share your views, this will help us grow..

Cheers :)

sertal70 commented 5 years ago

@rohit-khanna I implemented your solution and I can confirm it works, so at least it seems to be a viable workaround until the LB4 team will decide to support more that one auth strategy using the default binding mechanism. Which, IMHO, it should be the preferred way because there is no need to write&mantain a custom auth provider.

jannyHou commented 5 years ago

@sertal70 @rohit-khanna The team realized the authentication system should be extensible, story https://github.com/strongloop/loopback-next/issues/2312 and https://github.com/strongloop/loopback-next/issues/2311 are created for allowing people plugin more auth strategies. Those two stories are our high priorities now.

rohit-khanna commented 5 years ago

@jannyHou Thats Great. Looking forward to them ! :) @sertal70 thanks for validation of my workaround :)

sertal70 commented 5 years ago

@jannyHou very good news, thanks!

@rohit-khanna thanks to you for the suggestion!

jannyHou commented 5 years ago

This feature is supported after having the extension point :) See the new Authentication component Especially Registering Custom Auth Strategy

Swati-GOT commented 4 years ago

@rohit-khanna It would be great if can u please provide us with some code snippet for this example. I want to achieve the same thing but somehow I m not able to implement this example. Will be very thankful you can help me asap

dhmlau commented 4 years ago

@Swati-GOT, did you get a chance to look at the sample snippet in https://loopback.io/doc/en/lb4/Authentication-component-decorator.html#authentication-decorator?

  @authenticate({
    strategy: strategyName1,
    options: {option1: 'value1'}
  }, {
    strategy: strategyName2,
    options: {option2: 'value2'}
  })
rajkaran commented 10 months ago

@dhmlau @jannyHou I have implemented multiple authetication strategy but they are acting as OR either of them passes endpoint is accessible. Is there a way I can make it AND logic so that both needs to pass.

@authenticate({strategy: 'accesskey', options: {permission: 'write'}}, {strategy: 'jwt'})

If user provides valid access key but invalid jwt token still gets access to the endpoint. I want to prevent it, user should provide valid access key and jwt token to access resources. Please suggest how can I acheive this?