How to use JWT authentication with MDB Angular?

web
mobile

Topic: How to use JWT authentication with MDB Angular?
Published 04.06.2019 Updated 07.06.2019

Damian Gemza posted 5 months ago

While reading this article you will create a very simple Angular application using backend in Node and Express and Json Web Tokens.

The aim of this article is to introduce you to what JWT is and how to use it.

Introduction


Json Web Tokens, or JWT for short, is a mechanism for encoding data in JSON format, which can later be read in a web application. It consists in creating a token on the server side, which is inhibited by a cryptographic algorithm, e.g. RSA.

What can we use JWT for?

- User authentication,
- Exchange of sensitive data between applications / microservices

What does a typical JWT consist of?

The structure of the token can be divided into three parts - Header, Payload and Signature. Each of these parts is separated by a dot sign.

1) Header - the header of the token contains two information - about the type of token (in this case jwt) and about the type of cryptographic algorithm used for hash token (RSA / SHA256 / HMAC).

This is an example of a JWT header that is already decoded:

{
  "alg" "HS256",
  "typ" "JWT"
}


2) Payload - this is a token body in which claims are placed. They are entity records - mainly about the user, and additional data. There are three types of claims: registered, public and private.

Registered claims - a set of predefined claims that are not mandatory, but it is recommended to use them for the unified JWT type. These include iss (issuer), exp (expiration time), sub (subject), aud (audience) and others.

Public claims - These can be defined at will by those using JWTs. But to avoid collisions they should be defined in the IANA JSON Web Token Registry or be defined as a URI that contains a collision resistant namespace.

Private claims - these are custom claims that organizations undertake to support and respect. They cannot be named as registered or public claims.

Please note that the JWT has been developed as a compact data transport scheme. This means that the claims keys should be exactly 3 characters long, counting from the beginning of the word.

This is the example of a JWT payload that is decoded: 

{
  "sub" "1234567890",
  "name" "John Doe",
  "admin": true
}

3) Signature - to create a signature you have to take the header, payload, secret, algorithm defined in the header and sign it.

This looks like an example, the whole coded JWT:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

You can decode it on the website jwt.io.

Installation


All right, that's enough of that theory. It's time to do something more interesting. Let's move on to the code.

As this guide deals only with JWT and not with creating a user interface, I took the liberty of preparing a repository in which this interface is ready. Just clone the repository from my Github and then run the npm install command to install every needed by the application dependency.

Running the application


After installing all of the dependencies we need to run our application. Because JWT needs a server, I have already prepared its basic configuration.

Open the project directory in your favorite IDE, and then run two terminals.

In one of them run the server with node server/app.js command and in the other one run Angular application with npm run start command.

After starting both applications, open the web browser at http://localhost:4200.

As you can see, our application is very simple - it displays only one checkbox with todo downloaded from the server, and text. We are about to change that.

Implementing JWT middleware on the server


Let's take care of the server first. Open the server/app.js file, and add the following code to it just below line 2 (bodyParser):

const jwt = require('jsonwebtoken');
const expressJwt = require('express-jwt');

These two lines are responsible for importing two middleware: express-jwt and jsonwebtoken
. express-jwt is a middleware which works with Express as a JWT, and jsonwebtoken is the JWT library for Node.

Next, just below line 32, put the following code

app.use(expressJwt({secret: 'my-app-super-secret-key'}).unless({path: ['/api/auth']}));
app.post('/api/auth', (req, res) => {
  const body = req.body;

  const user = USERS.find(user => user.username === body.username && user.password === body.password);
  if (!user) {
    return res.sendStatus(401);
  }

  const token = jwt.sign({userID: user.id}, 'my-app-super-secret-key', {expiresIn: '2h'});
  res.send({token});
});

line 33 is responsible for initialization of the Express JWT mechanism. All routes except /api/auth should contain our token. Why did we exclude '/api/auth'? It's simple - auth is an endpoint for user authentication (this is where the JWT is generated and sent to the client).

Then in line 34, we check if the data sent in the request match our users data (both the username and the password must be found in the database), otherwise, the server will return the status of 401 - unauthorized.

If the data match, a token is created which contains the userID as payload and expires within 2 hours. After the token is created, it is sent to the client.

And at the very end, find the route 'api/todos', and replace its code with the following

app.get('/api/todos', (req, res) => {
  res.type('json');
  res.send(getTodos(req.user.userID));
});

Last piece of code - set the res.type to the json format, and then send to the client all the todo for the userID.

Implementing the JWT on the client-side


At the very beginning, you need to install one additional library. It is called @auth0/angular-jwt.

Install it with the following command:

npm install @auth0/angular-jwt

And then open the app.module.ts file, because we need to add some code there.

Just below the routes declaration add the following code:

export function tokenGetter() {
  return localStorage.getItem('access_token');
}

This function is used to download our token sent from the server from localStorage.

Then at the end of the imports table add the following code:

JwtModule.forRoot({
      config: {
        tokenGetter: tokenGetter,
        whitelistedDomains: ['localhost:4000'],
        blacklistedRoutes: ['localhost:4000/api/auth']
      }
    })

In the JwtModule configuration parameter we specified three things:

tokenGetter - the name of our function, which is responsible for retrieving the token,

whitelistedDomains - domain addresses that are acceptable when sending tokens. It is to these addresses that the token will be added to each HTTP query,

blacklistedRoutes - specific endpoints to which we do not want to send tokens with every HTTP request.

Route Guard


It's time to take care of the authentication of users. To do this we will use Guard, which will determine whether the user can go to the route '/todos' or not.
Open the third terminal in your application and then run the command below:

ng generate guard auth --spec=false --implements CanActivate

This command will create you a new auth.guard.ts file with implemented CanActivate interface.
Then open the app.module.ts file, and add the previously created AuthGuard to the  providers array.

After adding AuthGuard to our application we need to code it somehow. To do this, add the following code to the auth.guard.ts file:

constructor(private router: Router) {
  }

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):
    Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    if (localStorage.getItem('access_token')) {
      return true;
    }

    this.router.navigate(['login']);
    return false;
  }

The above code checks if there is an 'access_token' item in localStorage. If it exists, the user is authorized. If not, it redirects it to the route '/login' so it can log in.

The last thing to do with the AuthGuard is to protect the 'todos' route with it. To do this, open the app.module.ts file, and add canActivate: [AuthGuard] to line 17.

const routes: Routes = [
  {path: 'todos', component: TodosComponent, canActivate: [AuthGuard]},
  {path: 'login', component: LoginComponent},
  {path: '**', redirectTo: 'todos'}
];

ApiService


Open the api.service.ts file and replace its content entirely with the code below:

import {Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {map} from 'rxjs/operators';
import {Router} from '@angular/router';

@Injectable({
  providedIn: 'root'
})
export class ApiService {
  private _username = '';

  constructor(private http: HttpClient, private router: Router) {
  }

  public getTodos() {
    return this.http.get('api/todos');
  }

  public login(username: string, password: string) {
    return this.http.post<{ token: string }>('/api/auth', {username: username, password: password}).pipe(
      map(result => {
        localStorage.setItem('access_token', result.token);
        this._username = username;
      }));
  }

  public logout() {
    localStorage.removeItem('access_token');
    this.router.navigate(['login']);
  }

  public get isLoggedIn() {
    return (localStorage.getItem('access_token') !== null);
  }

  public get username() {
    return this._username;
  }
}

The ApiService code has been changed in its entirety. Let me explain from the top what has changed:

login method - in this method we execute the HTTP Post request to the address api/auth by sending user data (login and password), and then we subscribe to the value emitted by this action in order to receive the JWT and save it to localStorage.

logout method removes the 'access_token' key from localStorage so the application knows that we are not authorized because we do not have a saved JWT key, and then redirects us to the route '/login'.

The isLoggedIn field checks if there is an 'access_token' key in localStorage and returns true or false.

the username field returns the username used in the todos component.

Modifying the AppComponent


Now we need to make it possible to log out of our application (i.e. remove access_token).

To do this, open the app.component.html file, find the *ngIf="!isLoggedIn; else logged" directive,  and change it to the following one: *ngIf="!apiService.isLoggedIn; else logged".

Modifying the TodosComponent


The last change we need to make is to display the name of the currently logged in user in the todos panel.

To do this, open the todos.component.html file, and change there {{todo.name}} to {{apiService.username}}.

Tests


After all, save all the changes, open your browser at http://localhost:4200. You should get a login window. There are three users in the database: michael, john and bob. Each of them has a password set to todo.

Try to log in to one of the users. You will be redirected to the Todos component where a todo list is displayed for each user. If you click the logout button, the token will be removed from your browser's local storage, and you will need to log in again.

Summary


This article was not meant to describe everything about JWT authentication. The article was intended to describe the basics of JWT and how to use this mechanism to secure your applications.

I hope that after reading this article you will try JWT in your own application.


If something is unclear, or you would stop at some point and do not know what to do next, you should definitely check the Github repository of this project (branch with-jwt). There is a finished and working project there, which I discussed step by step.

Write
Please insert min. 20 characters.