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.04k stars 1.51k forks source link

Upgrading from rc.8 to rc.9 - JWT tokens not being stored by NbAuthService? #528

Closed Dikymon closed 6 years ago

Dikymon commented 6 years ago

Issue type

I'm submitting a ... (check one with "x")

Issue description

Current behavior: After upgrading from rc.8 to rc.9 and refactoring to use the new Auth strategy, the JWT token emitted from NbAuthService.getToken() is empty (type NbAuthSimpleToken) after a succesful sign-in. I have verified that the token is correctly returned from the 'token.getter' method. What am i missing?

Expected behavior: Token is emitted on AuthService.getToken() as in rc.8.

Related code:

NbPasswordAuthStrategy.setup({
        name: 'email',
        baseEndpoint: '/auth/',
        token: {
          class: NbAuthJWTToken,
          key: 'token'
        },
        ....

Angular, Nebular

Angular 6, Nebular rc.9

<!--
Check your `package-lock.json` or locate a `package.json` in the `node_modules` folder.
-->
nnixaa commented 6 years ago

Hey @Dikymon, thanks for reporting. Could you provide a complete configuration of the auth module? Do you use a custom auth form or provided? And where you are trying to access the getToken() method?

Dikymon commented 6 years ago

First off - thank you for a great library. Here is he auth configuration I am using

...NbAuthModule.forRoot({
    strategies: [
      NbPasswordAuthStrategy.setup({
        name: 'email',
        baseEndpoint: '/auth/',
        token: {
          class: NbAuthJWTToken,
          key: 'token'
        },
        login: {
          rememberMe: true,
          endpoint: 'login',
          method: 'post',
          redirect: {
            success: '/pages/main'
          }
        },
        register: {
          endpoint: 'register',
          method: 'post'
        },
        logout: {
          endpoint: 'logout',
          method: 'get',
          redirect: {
            success: '/pages/welcome'
          }
        },
        requestPass: {
          endpoint: 'request-password',
          method: 'post',
          defaultErrors: [''],
          defaultMessages: ['']
        },
        resetPass: {
          endpoint: 'reset-password',
          method: 'put',
          resetPasswordTokenKey: 'reset_token_key',
          redirect: {
            success: '/',
            failure: null
          }

        },
        refreshToken: {
          endpoint: 'refresh-token',
          method: 'post',
          redirect: {
            success: null,
            failure: null,
          },
        }
      }),
    ],
    forms: {},
  }).providers

I am using a custom form, in the sense that I copied the auth components and made minor layout and wording changes. I updated those to use 'strategy' instead of 'provider' with reference to rc.9 component source - but most changes were just re-naming/typing variables.

After succesful login, the user is redirected to a page with a Guard

canActivate() {
    return this.authService.isAuthenticated();
}

Subscribing to authService.getToken()

this.authService.getToken().subscribe((t: NbAuthJWTToken) => console.log(t));

yields

NbAuthSimpleToken {token: ""}

when sign-in is successful. In rc.8 it returned

NbAuthJWTToken {token: "eyJhbGciOi...."}
nnixaa commented 6 years ago

@Dikymon in your login.component.ts, could you console.log the result variable here:

this.service.authenticate(this.strategy, this.user).subscribe((result: NbAuthResult) => {
console.log(result);

It looks to me, that the NbAuthSimpleToken {token: ""} is an old token value currently stored under auth_app_token key (which cannot be presented and NbAuthJWTToken token as there is no metadata for in in the previous version token format. And it is not being overwritten by a new value coming from the server. Now we need to find out why.

Dikymon commented 6 years ago

Here is the output, seems okay?

errors: []
messages: ["You have been successfully logged in."]
redirect:"/pages/main"
response:
HttpResponse
    body:
        token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InRlc3RAdGVzdC5kayIsInJvbG..."
        user: {email: "test@test.com", role: "user"}
    headers: HttpHeaders {normalizedNames: Map(0), lazyUpdate: null, lazyInit: ƒ}
    ok: true
    status: 200
    statusText: "OK"
    type: 4
    url: "https://localhost/auth/login"
success: true
token: NbAuthJWTToken {token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6I…"}
nnixaa commented 6 years ago

Right, this part is okay. And If you check the Application -> Localstorage tab in the chrome dev tools, could you post here what is stored there under then auth_app_token key? The other possible issue is that it might not be able to parse the stored token for some reason here https://github.com/akveo/nebular/blob/3428ec3777afa48e87b65cd7ad47195987da0898/src/framework/auth/services/token/token-parceler.ts#L35, falling back to a default token class.

Dikymon commented 6 years ago

key 'app_auth_token' seems to store it correctly:

name: "nb:auth:jwt:token"
value: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InRlc3RAdGVzdC5kayIsInJvbG...
nnixaa commented 6 years ago

Okay, now please open token-parceler.js on line 23 and console.log like this:

var tokenPack = JSON.parse(value);
console.log(tokenPack);
tokenClass = this.getClassByName(tokenPack.name) || this.fallbackClass;
console.log(tokenClass);
tokenValue = tokenPack.value;
Dikymon commented 6 years ago

It prints tokenPack

name: "nb:auth:jwt:token"
value: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InRlc3RAdGVzdC5kayIsInJvbGUiOiJhZHVybyIsImZpcnN0TmFtZSI6IkFiZSIsImxhc

then throws an exception on the call to .getClassByName.

Error TypeError: this.tokenClasses.find is not a function
    at NbAuthTokenParceler.push../node_modules/@nebular/auth/services/token/token-parceler.js.NbAuthTokenParceler.getClassByName (token-parceler.js:40)
    at NbAuthTokenParceler.push../node_modules/@nebular/auth/services/token/token-parceler.js.NbAuthTokenParceler.unwrap (token-parceler.js:25)
    at NbTokenLocalStorage.push../node_modules/@nebular/auth/services/token/token-storage.js.NbTokenLocalStorage.get (token-storage.js:56)
    at NbTokenService.push../node_modules/@nebular/auth/services/token/token.service.js.NbTokenService.get (token.service.js:66)
    at NbAuthService.push../node_modules/@nebular/auth/services/auth.service.js.NbAuthService.getToken (auth.service.js:28)
    at NbAuthService.push../node_modules/@nebular/auth/services/auth.service.js.NbAuthService.isAuthenticated (auth.service.js:43)
    at UserGuard.push../src/app/pages/pages.guard.ts.UserGuard.canActivate (pages.guard.ts:16)

Which explains the use of the fallback class.

nnixaa commented 6 years ago

That's great, we found the cause. What would be there if you print console.log(this.tokenClasses)?

Dikymon commented 6 years ago

this.tokenClasses:

ƒ NbAuthJWTToken() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
nnixaa commented 6 years ago

This is strange. It should be an array as it is provided by the NbAuthModule. Is NB_AUTH_TOKENS by any chance provided ({ provide: NB_AUTH_TOKENS, ... }) somewhere in your modules?

Dikymon commented 6 years ago

Yes! That was it. Does that conflict with in some way?

Thank you so much for your help, I hope it was not a complete waste of your time.

nnixaa commented 6 years ago

Yes! That was it. Does that conflict with in some way?

Starting with rc.9 there is no need to provide a token (or tokens) anymore, just mention it in the strategy configuration ({ token: { class: NbSomeTokenClass } }and it will do the rest.

No problem, glad we resolved it.

I will leave the issue opened as supressing all of the errors in the token parceler class looks like a not very good idea as it hides such issues.