Angular2-jwt: Angular 5 Support

Created on 6 Nov 2017  ·  39Comments  ·  Source: auth0/angular2-jwt

Getting the following warning when upgrading to Angular 5.0.0

npm WARN [email protected] requires a peer of @angular/core@^2.0.0||^4.0.0 but none was installed.
npm WARN [email protected] requires a peer of @angular/http@^2.0.0||^4.0.0 but none was installed.
stale

Most helpful comment

In my app.module :

import { JwtModule } from '@auth0/angular-jwt'

@NgModule({
  bootstrap: [AppComponent],
  imports: [
    // ...
    HttpClientModule,
    JwtModule.forRoot({
      config: {
        tokenGetter: () => {
          return localStorage.getItem('access_token');
        },
        whitelistedDomains: ['you API url']
      }
    })
  ]
})

In authentication.service.ts

import { JwtHelperService } from '@auth0/angular-jwt'

@Injectable()
export class AuthenticationService {
  constructor(private http: HttpClient, private jwtHelperService: JwtHelperService) {}

  loggedIn() {
    const token: string = this.jwtHelperService.tokenGetter()

    if (!token) {
      return false
    }

    const tokenExpired: boolean = this.jwtHelperService.isTokenExpired(token)

    return !tokenExpired
  }

... other methods such as reset password etc...
}

In the auth-guard.service.ts

import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { AuthenticationService } from '../authentication/authentication.service';

@Injectable()
export class AuthGuard implements CanActivate {

  constructor(
    private auth: AuthenticationService,
    private router: Router,
  ) {}

  canActivate(): boolean {
    if (this.auth.loggedIn()) {
      return true;
    }  else {
      localStorage.removeItem('token');
      this.router.navigateByUrl('/login');
      return false;
    }
  }
}

and in the app.routing.ts we have a canActivate: [AuthGuard]

Hope that helps :)

All 39 comments

Any news?
I am starting to upgrade my app to Angular 5.0.0 too.

Same here. No signs of the project ever seeing the release of version 1.0.

I'm currently being stopped from completing the upgrade of my project to Angular V5 because of this library also (The HttpModule was deprecated)... Could you shed some light on the progress of the V1 update please @chenkie ?

Errr... The newest release is 1.0.0-beta.9, which very much uses HttpClientModule instead of HttpModule.

From the front page:

For the version 1.0 pre-release with HttpInterceptor support, see the v1.0 branch.

The issue lies here:

    "@angular/core": "^2.0.0||^4.0.0",
    "@angular/http": "^2.0.0||^4.0.0",

I switched to the 1.0.0 beta branch but migrating from 0.2 to 1.0.0 seems to be more complicated than I thought, a migration tutorial would be very nice !

Can you get the pre-release versions from npm?

Yes just npm i -S @auth0/angular-jwt and you will have the latest beta version installed

@Lakston Thanks! I see the latest version from npm is 0.2.3, which was released back in May, does this work with interceptors/Angular 5?

Edit: Nevermind, I just realised it's a different npm package, angular2-jwt vs angular-jwt

The package I've linked you to support Angular 5 and I guess they chose to rename it since the angular2 in the name wasn't relevant anymore. I haven't been able to set up the beta version though that's why I was asking for a tutorial :)

You can see the branch and the readme here

Also, it should eventually support Angular's HttpClient

Follow up : after some tinkering we managed to use the version 1.0.0 beta in our app successfully so I can confirm that it works with angular 5+.

The source code is really small though so you could eventually implement it by hand without using the lib, we chose to still use the package but if it is not actively developed we might just go with a manual implementation in the future.

Hope that helps.

@Lakston could you share some example of your implementation? It will be very nice, because I can't figure it out how to use this beta version in angular 5.

In my app.module :

import { JwtModule } from '@auth0/angular-jwt'

@NgModule({
  bootstrap: [AppComponent],
  imports: [
    // ...
    HttpClientModule,
    JwtModule.forRoot({
      config: {
        tokenGetter: () => {
          return localStorage.getItem('access_token');
        },
        whitelistedDomains: ['you API url']
      }
    })
  ]
})

In authentication.service.ts

import { JwtHelperService } from '@auth0/angular-jwt'

@Injectable()
export class AuthenticationService {
  constructor(private http: HttpClient, private jwtHelperService: JwtHelperService) {}

  loggedIn() {
    const token: string = this.jwtHelperService.tokenGetter()

    if (!token) {
      return false
    }

    const tokenExpired: boolean = this.jwtHelperService.isTokenExpired(token)

    return !tokenExpired
  }

... other methods such as reset password etc...
}

In the auth-guard.service.ts

import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { AuthenticationService } from '../authentication/authentication.service';

@Injectable()
export class AuthGuard implements CanActivate {

  constructor(
    private auth: AuthenticationService,
    private router: Router,
  ) {}

  canActivate(): boolean {
    if (this.auth.loggedIn()) {
      return true;
    }  else {
      localStorage.removeItem('token');
      this.router.navigateByUrl('/login');
      return false;
    }
  }
}

and in the app.routing.ts we have a canActivate: [AuthGuard]

Hope that helps :)

@Lakston You are awesome. Thank you so much for saving my day. God bless you

Just to clarify, is there going to be a new release with support for Angular 5? Otherwise, how to update to Angular 5?

@rsazima There is. It's named @auth0/angular-jwt now.

https://www.npmjs.com/package/@auth0/angular-jwt

@rsazima Yes there is, it's the alpha branch I linked to, and if you look at the code I pasted a few posts up you'll notice I import from @auth0/angular-jwt which is the updated version supporting Angular 5. @LosD linked you to the repo, between this and the code I pasted you should be able to set it up.

Thank you!

@Lakston Your solution seems awesome! But just to be sure, will your repo be integrated to this one ? Is it an official release from auth0 and will it be maintained ? Thanks

Hey @DenisLaboureyras , the repo I've linked to is not mine, it is indeed a repo from the same author as the angular2-jwt library, they just renamed it so there is no confusion. You can actually see both repo if you go to : https://github.com/auth0/angular2-jwt and you click on "Branch: master" you can switch to "Branch: 1.0.0", same author, same code but updated for Angular 4.3+ and the use of interceptors.

So is it official ? Yes.

Will it be maintained ? I can not answer that for the auth0 maintainers, I hope so ! :)

hope that helps !

OK thanks for your answer !!

So, with this configuration as seen on npm and posted by @Lakston, it seems that every single requests that gets sent with the new HttpClient will have the configuration by this library.

Is there any way to NOT use it when using a specific request? For example, I do not want to send the Authorization headers for a /login request. I liked the distinction between having a distinct Http for regular requests and a authHttp for requests that require a jwt.

How does one do this now?

@kamok As far as I can see, there's not really a way, short of having your own interceptor somehow skip the JWT one (sadly, I'm not really sharp on these), or have your tokenGetter just not return a token, which would probably be a bit of a pain, since it's not called with any kind of parameters for you to identify the request, so you'd have to use some kind of global variable to control it, which doesn't really seem practical or safe.

Of course, if your login service is on another domain or port, you could just not whitelist it.

In practice, it hasn't really been a problem, at least for me. Normally, the login endpoint simply ignores auth tokens.

I'm using Angular 5.1.0 along with angular2-jwt 0.2.3 and its working just fine. I understand that Http is deprecated, however, for now, you can still use it. I understand that its not ideal and not a best practice since its deprecated, but no issues with authHttp for me. Http hasn't been fully decommissioned just yet

So, I just came across this. Only place I've ever seen it:

capture

Taken from the top of this page: https://auth0.com/blog/introducing-angular2-jwt-a-library-for-angular2-authentication/

apparently angular2-jwt is deprecated and auth0 did a pretty terrible job of letting users know that. If you follow this tutorial: https://auth0.com/blog/real-world-angular-series-part-1/ and look at its source code on github, you can see how they use JWTs without this library. I'm going to take a stab at removing angular2-jwt from my code and see if I can get it working with the new HttpClient

If you do find out please post on here as im pretty sure we all would like to know.

Thank you for pointing that out @rhino5oh ! I wasn't aware that the library was deprecated....

It indeed is pretty easy to implement the functionality to add the Authorization headers into http requests... especially with the introduction of Http Interceptors.

Cheers!

I can contribute another article on how to implement it without a library, made by the author of angular2-jwt, here

It's not complicated indeed and we thought about doing it 'by hand' too but in the end used the alpha branch we talked about earlier in this post, but when we have some time we'll definitely do it ourselves.

@tidusjar it is pretty straightforward to remove angular2-jwt and replace it with the new HttpClient. Here are some examples...

Old way with angular2-jwt:

    getPersonById(id: string): Observable<Person> {
            return this.authHttp.get('/api/person/' + id)
            .map(res => res.json())
            .catch((error:any) => Observable.throw(error || 'Server Error'));
    }

New way with HttpClient:

    constructor(private http: HttpClient) { }

    private get authHeader(): string {
        return 'Bearer ${localStorage.getItem('access_token')}';
    }

   getPersonById(id: string): Observable<Person> {
        return this.http.get<any>('/api/person/' + id, {
            headers: new HttpHeaders().set('Authorization', this.authHeader)
            })
            .catch(this.handleError)
    }

With HttpClient, you'll notice I've added middleware to set the header with the JWT. I have a getter to retrieve the string 'Bearer ' + JWT. This is a bit different than the link that @Lakston posted. In that link, the author sets up an Http Interceptor provider. I did not do that (yet - I probably will in the future). It is not necessary but cleans up the code a bit because you don't have to put the middleware on every request. The interceptor does it automatically.

Also, you can remove ".map(res => res.json())" because it is implied. However, if you were previously returning something specific from the json in order to do something with it like I do here (res.json().insertedIds[0])...

    insertPerson(person: Person): Observable<string> {
        let headers = new Headers({ 'Content-Type': 'application/json' });
        let options = new RequestOptions({ headers: headers });

        return this.authHttp.post('/api/insert-person', { person }, options)
                .map(res => res.json().insertedIds[0])
                .catch((error:any) => Observable.throw(error || 'Server Error'));
    }

...Then the new way needs to be like this:

    insertPerson(person: Person): Observable<string> {

        return this.http.post<any>('/api/insert-person', { person }, {
            observe: 'response',
            headers: new HttpHeaders().set('Authorization', this.authHeader)
            })
            .map(res => res.body.insertedIds[0])
            .catch(this.handleError)
    }

Notice that I have added "observe: 'response'" to the middleware so that I can work with the entire response. Then, I call out specifically what I want from the json, in this case that is res.body.insertedIds[0]. You will now need to use ".body" instead of ".json()"

How to proceed with Angular 5+

Due to the lack of info, I wrote a Medium post about this. I'm going to summarize it in Markdown format here as well to help you guys out.


Yes, angular2-jwt is deprecated. This means if you're using Angular 5+, you should use auth0/angular-jwt

Okay, so what do I need to change, then?

Well, Making the switch from the Angular CLI's default angular2-jwt to auth0/angular-jwt isn't too bad. But I'm going to give you a little explanation on why the change occurred and what's really going on.

Here's how the auth used to work with angular2-jwt and the default Angular CLI setup:

  • On the server side, you encode a small user object into a jwt token using something like this: jwt.sign(signableObject, config.jwtSecret). Often times that signableObject contains just the _id of the user, maybe a few other properties. After getting that token, you send a response along the lines of res.json({ token : yourTokenGoesHere })
  • On the client side, this token is decoded using angular2-jwt, which produces that user object again, and you know you're logged in.

  • The token is stored in localStorage, and every time a user reloads the page, this token is decoded to get the user's fields (again, typically just a small object).

Basically here's what's happening _instead_, now. Here are the changes we need to make.

  • On the SERVER, Instead of encoding the small user object into a token, you're just sending the user object AND the token as part of the authentication response. So your res object response becomes:
    res.json({ user: someSmallUserObject, token: yourToken })

  • On the CLIENT, what you want to do, is save the user object straight into your localStorage by calling JSON.stringify(res.user). -- _Note: yes, you stringify it._

  • You ALSO, on the CLIENT, save the token from that same res object, into localStorage under the key access_token by calling localStorage.setItem('access_token', res.token);.

  • When the page is reloaded, if you want the user again, you call JSON.parse(localStorage.getItem('user'); -- _Note, every time you call JSON.parse you should wrap this in a try-catch block._

  • When the page is reloaded, you can "assume" a user is logged in if they have the access token in local storage by calling localStorage.getItem('access_token').
    -- _Note: This “assumption" is safe even if you have auth guards, because presumably when someone accesses any auth guarded route you have a subsequent API call that will contain a bad authorization header and will return a 401 (UNAUTHORIZED) response and the user will not be able to proceed and/or be logged completely out by using localStorage.removeItem('access_token');_

You will notice that now you don't ever bother with decoding res.token on the client side. You store in localStorage, that is all. Decoding a token on the client side is no longer recommended, hence the deprecation.

So what about making API calls? How does the authorization header get sent. (Remember, we need that token sent with every request).

That's where auth0/angular-jwt comes in. As explained in the @auth0/angular-jwt documentation, you import the module when you first bootstrap your app, and create a method called tokenGetter that retrieves this token from your localStorage.

import { JwtModule } from '@auth0/angular-jwt';
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  bootstrap: [AppComponent],
  imports: [
    // ...
    HttpClientModule,
    JwtModule.forRoot({
      config: {
        tokenGetter: () => {
          return localStorage.getItem('access_token');
        },
        whitelistedDomains: ['localhost:3001'] // example
      }
    })
  ]
})
export class AppModule {}

that tokenGetter is automatically called with HttpClient API requests. So you're good to go from here.

This might look like a complicated process if you're new to authentication or if you never understood how it was working in the first place, but it's not much different at all, and it's certainly not complicated or daunting. You can do it.

I hope this helped.

i have a question, if i shouldn't decode jwt on client side, how can i make role based auth on my app ?

@GoLucas you pass a minimal user object down from the server-side, along with the token, something like this in Express:

res.json({
  token: 'your_token_here',
  user: { _id: 'some_id', name: 'some_name', role: 'some_role' }
})

On the client, you store both the token and the JSON.stringify'd user into localStorage.

This was confusing to me at first, that you're only getting a minimal piece of the user. But it makes sense. When you reach the first screen after logging in, for example, a dashboard, it's the API call from the dashboard that is retrieving the other data to show to the UI, and that's great. You really only need a minimal user object upon logging in. I know personally I add 1 or 2 app-specific user profile details to this object to avoid what would be a redundant API call, but you shouldn't be passing in a huge user to localStorage with embedded docs, etc.

Excuse the obtuse question but why is decoding a token on the client side is no longer recommended?

Along the same line, if we don't control the server that creates the JWT token, then we basically have to decode it on the client. This isn't an outlandish scenario, so please leave in the ability to decode the token even if it isn't recommended in most cases.

Our team understanding was that, sticking to the standard, client adds also openid to scope parameter when sending a token request, so that an OpenID Connect/OAuth2 server (e.g. like ASOS) could send back to client (apart from access token and refresh token) the identity token. That one contains also some predefined user info, packed and encoded within that. So in this scenario you should need to decode the identity token in order to extract those info on the client.

Not sure sending those few user info with a custom approach is an improvement.

Thoughts? Thanks all

Wow, I'm super confused about this library. Is it deprecated or not? It will be removed or replaced by other solution or not? I also saw this message posted by @rhino5oh, but now this link returns 404 and I see new releases. What is going on?

Thank you for saving me! God bless you....

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If you have not received a response for our team (apologies for the delay) and this is still a blocker, please reply with additional information or just a ping. Thank you for your contribution! 🙇‍♂️

Was this page helpful?
0 / 5 - 0 ratings

Related issues

leosvelperez picture leosvelperez  ·  5Comments

fabiodomingues picture fabiodomingues  ·  5Comments

wannabegeek picture wannabegeek  ·  3Comments

hang321 picture hang321  ·  4Comments

Eddman picture Eddman  ·  3Comments