3.6: Improving Authentication

Right now, when we check to see if a user is logged in - we're only checking if there is an item in their local storage with the key 'token'.

As explained previously, this isn't entirely helpful for us. Is it invalid or expired? We don't know.

To help us improve our login and register method, we'll use a library for this provided by Auth0 called angular2-jwt.

Look over the docs if you'd like.

In the docs, it lists the 'key features' of the library:

  • Send a JWT on a per-request basis using the explicit AuthHttp class

  • Decode a JWT from your Angular 2 app

  • Check the expiration date of the JWT

  • Conditionally allow route navigation based on JWT status

Sounds perfect!

Installing and Configuring angular2-jwt

Make sure you are in the root folder of your SPA project and run the following command:

npm install @auth0/angular-jwt --save

Next, in our app.module.ts file, we'll import the library

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

//  ...

import: [
    //  ...
    JwtModule.forRoot({
      config: {
        tokenGetter: () => {
          return localStorage.getItem('token');
        },
        whitelistedDomains: ['localhost:5000']
      }
    })
]

Updating loggedIn() method

In our auth.service.ts class, let's start by injecting the JwtHelperService provided by the library into our constructor.

constructor(private _http: HttpClient,
            private _jwtHelperService: JwtHelperService) { }

Update the method in our service to check if a user has an unexpired token or not:

loggedIn() {
    const token = this._jwtHelperService.tokenGetter();

    if (!token) {
        return false;
    }

    return !this._jwtHelperService.isTokenExpired(token);
}

Decoding the token

Next, we'll add a property to hold the information in the token and the JwtHelper.

decodedToken: any;

Update the login() method to use the jwtHelperService:

login(model: any) {
    return this._http.post<AuthUser>(this.baseUrl + 'login', model, { headers: new HttpHeaders()
        .set('Content-Type', 'application/json') })
        .map(user => {
        if (user) {
            localStorage.setItem('token', user.tokenString);
            this.decodedToken = this._jwtHelperService.decodeToken(user.tokenString);   // <--- Added
            this.userToken = user.tokenString;
        }
        });
}

Displaying the Username in the Nav

In the nav.component.html, let's use the AuthService to display the username when a user is logged in:

What we currently have:

Welcome, user!

Let's change it to:

Welcome, {{ authService.decodedToken?.unique_name }}!

The decodedToken? has a conditional null operator. If the page loads and it can't find this - it won't crash.

Retrieving User Information ASAP

Right now, if we're logged in and then refresh the page - we lose our username in the navbar and our information.

Instead of just loading the information in the navbar, we'll try loading all user information as soon as the root component of the application is initialized (before the nav component).

In app.component.ts, update it to the following:

export class AppComponent implements OnInit {
  constructor(
    private _authService: AuthService,
    private _jwtHelperService: JwtHelperService) {}

  ngOnInit() {
    const token = localStorage.getItem('token');
    const user: User = JSON.parse(localStorage.getItem('user'));

    if (token) {
      this._authService.decodedToken = this._jwtHelperService.decodeToken(token);
    }
  }
}

Now, we should be able to refresh the page without losing our status and information.

Testing

Open up your browser, and try logging in!

Last updated