akveo / nebular

:boom: Customizable Angular UI Library based on Eva Design System :new_moon_with_face::sparkles:Dark Mode
https://akveo.github.io/nebular
MIT License
8.06k stars 1.51k forks source link

Adding Multiple OAuth2 Strategies Not working #682

Open carneles opened 6 years ago

carneles commented 6 years ago

I implemented more than one strategy like this:

strategies: [
          NbPasswordAuthStrategy.setup({
            name: 'xxx',
            baseEndpoint: '/api/',
            login: {
              endpoint: 'login/xxx',
              method: 'post'
            },
            token: {
              class: NbAuthJWTToken,
              key: 'data.token'
            },
            errors: {
              key: 'error.message'
            },
          }),
          NbPasswordAuthStrategy.setup({
            name: 'yyy',
            baseEndpoint: '/api/',
            login: {
              endpoint: 'login',
              method: 'post'
            },
            register: {
              endpoint: 'register',
              method: 'post'
            },
            token: {
              class: NbAuthJWTToken,
              key: 'data.token'
            },
            errors: {
              key: 'error.message'
            }
          }),

But when trying to use xxx strategy, it always returns "There is no Auth Strategy registered under 'xxx' name" error:

this.authService.authenticate('xxx', params)
          .subscribe((result: NbAuthResult) => { 

Was trying to debug by adding log build result of auth.service.js:

function (strategyName) {
        console.log('Strategies: ', this.strategies);
        var found = this.strategies.find(function (strategy) { return strategy.getName() === strategyName; });
        if (!found) {
            throw new TypeError("There is no Auth Strategy registered under '" + strategyName + "' name");
        }
        return found;
    };

But in the result the strategies does not have 'name' field:


(2) [NbPasswordAuthStrategy, NbPasswordAuthStrategy]
0
:
NbPasswordAuthStrategy
defaultOptions
:
NbPasswordAuthStrategyOptions {baseEndpoint: "/api/auth/", login: {…}, register: {…}, requestPass: {…}, resetPass: {…}, …}
http
:
HttpClient {handler: HttpInterceptingHandler}
options
:
{baseEndpoint: "/api/", login: {…}, register: {…}, requestPass: {…}, resetPass: {…}, …}
route
:
ActivatedRoute {url: BehaviorSubject, params: BehaviorSubject, queryParams: BehaviorSubject, fragment: BehaviorSubject, data: BehaviorSubject, …}
__proto__
:
NbAuthStrategy
1
:
NbPasswordAuthStrategy```
alain-charles commented 6 years ago

Hi @carneles

If we have a look at the nbStrategiesFactoryfunction, responsible for creating the strategies you declare, we see :

export function nbStrategiesFactory(options: NbAuthOptions, injector: Injector): NbAuthStrategy[] {
  const strategies = [];
  options.strategies
    .forEach(([strategyClass, strategyOptions]: [NbAuthStrategyClass, NbAuthStrategyOptions]) => {
      const strategy: NbAuthStrategy = injector.get(strategyClass);
      strategy.setOptions(strategyOptions);

      strategies.push(strategy);
    });
  return strategies;
}

If you declare two different strategies with the same class (i.e. two NbPasswordAuthStrategy in your case), then for the second(yyy), the injector.get(strategyClass)points to the first (xxx). The next line (setOptions()) overwrites all the properties of 'xxx' including name. => So you can use only yyy.

@nnixaa it seems that we cannot for now declare two strategies with the same class. Would it be interesting to do something for troubleshoot that?

nnixaa commented 6 years ago

Hey @carneles, thanks for reporting and @alain-charles, thanks for digging in. Yes, it looks like we cannot have two strategies with the same class and this is very unfortunate.

As for a workaround, you can create a second strategy class, extend it from the NbPasswordStrategy and provide it:

@Injectable()
export class NbSecondPasswordStratey extends NbPasswordStrategy {}

...
providers: [
  ...
  NbSecondPasswordStratey,
]

Then you can declare both strategies.

But having two strategies with the same class under different names was our original intention, looks like we missed it.

maihannijat commented 5 years ago

Could you please explain it little more?

I created another class and extended the Auth strategy as follow:

import {Injectable} from '@angular/core';
import {NbOAuth2AuthStrategy} from '@nebular/auth';

@Injectable()
export class Nb0Auth2AuthFacebookStrategy extends NbOAuth2AuthStrategy {
}

I added in the Core Modules:

export const NB_CORE_PROVIDERS = [
  ...DataModule.forRoot().providers,
  ...NbAuthModule.forRoot({
    strategies: [
      NbPasswordAuthStrategy.setup({
        name: 'email',
        baseEndpoint: config.API_URL + 'auth/',
        login: {
          endpoint: 'sign-in',
        },
        register: {
          endpoint: 'sign-up',
        },
        logout: {
          endpoint: 'sign-out',
        },
        requestPass: {
          endpoint: 'password-request',
        },
        resetPass: {
          endpoint: 'password-reset',
        },
        token: {
          class: NbAuthJWTToken,
          key: 'token',
        },
      }),
      Nb0Auth2AuthFacebookStrategy.setup({
        name: 'facebook',
        clientId: '249362002407540',
        authorize: {
          endpoint: 'https://www.facebook.com/v3.2/dialog/oauth',
          responseType: NbOAuth2ResponseType.TOKEN,
          scope: 'basic_info',
          redirectUri: 'http://localhost:4200/auth/callback-facebook',
        },
      }),
    ],
    forms: {
      login: {
        socialLinks: socialLinks,
      },
      register: {
        socialLinks: socialLinks,
      },
    },
  }).providers,

Where do I need to add it as provider? In the login? And Do I need to leave the class empty?

nnixaa commented 5 years ago

@maihannijat yes, you can leave the class empty sinse you just need a new service. As for the provider - you can put it into the NB_CORE_PROVIDERS list.

maihannijat commented 5 years ago

@nnixaa the above should work fine according to your explanation, but it does not. It still has the same behavior.

Error during template compile of 'AppModule' Function calls are not supported in decorators but 'Nb0Auth2AuthFacebookStrategy' was called in 'CoreModule' 'CoreModule' references 'NB_CORE_PROVIDERS' 'NB_CORE_PROVIDERS' calls 'Nb0Auth2AuthFacebookStrategy'.

TheNomo3000 commented 5 years ago

Hey @nnixaa could you solve it? I have the same problem when trying to do it with more than one NbOAuth2AuthStrategy.

@Injectable()
export class NbGoogleOAuth2Strategy extends NbOAuth2AuthStrategy {}

@Injectable()
export class NbFacebookOAuth2Strategy extends NbOAuth2AuthStrategy {}

export const NB_CORE_PROVIDERS = [
  ...NbAuthModule.forRoot({
    strategies: [
      NbDummyAuthStrategy.setup({
        name: 'email',
        delay: 3000,
      }),
      NbOAuth2AuthStrategy.setup({
        name: 'google',
        clientId: '*******JoYA',
        clientSecret: '',
        authorize: {
          endpoint: 'https://accounts.google.com/o/oauth2/v2/auth',
          responseType: NbOAuth2ResponseType.TOKEN,
          scope: 'https://www.googleapis.com/auth/userinfo.profile',
          redirectUri: 'http://localhost:4100/example/oauth2/callback',
        }
      }),
      NbFacebookOAuth2Strategy.setup({
        name: 'facebook',
        clientId: '****3259',
        clientSecret: '***aea2f6',
        authorize:{
          endpoint: 'https://www.facebook.com/v3.2/dialog/oauth',
          responseType: NbOAuth2ResponseType.TOKEN,
          redirectUri: 'http://localhost:4200/callback',
        }
      }),
    ],
    forms: {
      login: {
        socialLinks: socialLinks,
      },
      register: {
        socialLinks: socialLinks,
      },
    },
  }).providers
];

Error: image

Following screenshots are from the debug which comes from the file: auth.service.js > this.strategies...

tempsnip

tempsnip2

nnixaa commented 5 years ago
export const NB_CORE_PROVIDERS = [
  ...NbAuthModule.forRoot({
    // ...
  }).providers,
  NbGoogleOAuth2Strategy,
  NbFacebookOAuth2Strategy,
];

Adding strategies as services should do the trick. Haven't been able to test the code though, let me know how it goes.

khatrizeeshan commented 5 years ago

Have tried didn't help

TheNomo3000 commented 5 years ago
export const NB_CORE_PROVIDERS = [
  ...NbAuthModule.forRoot({
    // ...
  }).providers,
  NbGoogleOAuth2Strategy,
  NbFacebookOAuth2Strategy,
];

Adding strategies as services should do the trick. Haven't been able to test the code though, let me know how it goes.

Hey @nnixaa , we have tried and it didn't work, apreciate help. Any other ways???

nnixaa commented 5 years ago

Right, one more step and it should be fine as a workaround:


@Injectable()
export class NbGoogleOAuth2Strategy extends NbOAuth2AuthStrategy {
  static setup(options: NbOAuth2AuthStrategyOptions): [NbAuthStrategyClass, NbOAuth2AuthStrategyOptions] {
    return [NbGoogleOAuth2Strategy, options]; // HERE we make sure our strategy reutrn correct class reference
  }
}

@Injectable()
export class NbFacebookOAuth2Strategy extends NbOAuth2AuthStrategy {
  static setup(options: NbOAuth2AuthStrategyOptions): [NbAuthStrategyClass, NbOAuth2AuthStrategyOptions] {
    return [NbFacebookOAuth2Strategy, options]; // HERE we make sure our strategy reutrn correct class reference
  }
}
TheNomo3000 commented 5 years ago

Yes, it works correctly @nnixaa Thanks!

dalayza commented 5 years ago

Good evening, and how can I send the user data login google to show your photo and name in the headers?

neilhem commented 5 years ago

Was it fixed in version 4.4 or higher?

abdullah2993 commented 4 years ago

any update on the fix or documentation update?

neilhem commented 4 years ago

Still not possible to create multiple strategies

gabriel-messas commented 3 years ago

@nnixaa I did not get it, guys. Where exactly should I add each portion of code?

nloyola commented 3 years ago

Please update the documentation. Reading the comments on this issue is not straightforward. I am not sure where to put this code and how to make this work.

SZOKOZ commented 1 year ago

I find that replacing the static setup in my extended implementation in order to return an array of my custom class and options works well for me.

@Injectable()
export class NbCustomAuthStrat extends NbPasswordAuthStrategy {
  protected http: HttpClient;
  static setup(options): [NbAuthStrategyClass, NbPasswordAuthStrategyOptions] {
    return [NbCustomAuthStrat, options];
  }
}