JWT with Express and Angular

January 2018

JWT is becoming quite popular these day. What is it and how does it work? Let's learn more about it in this short tutorial.

What's JWT?

JWT is short for JSON Web Tokens. One use case is to help you with authentication. It doesn't help you with initial authentication, but rather with authentication for returning users. So it is actually comparable to regular session handling.

As the name JWT suggests, "tokens" are the centerpiece of the technology. But what are those tokens? To understand, let's have a look at two main parts of JWT. Those would be the encryption and the decryption methods. The encryption is the so called "signing" function. It has the following signature:

jwt.sign(JSON Data, secret:string) => token:string

Aha, so here we have the token. So the jwt.sign function takes JSON Data, usually the user-json, combines it with a secret, and produces a token. The counterpart to the encryption is called, in Express at least, the verify (and also decode) function. It has the following signature:

jwt.decode(token, secret: string) => JSON Data

If you provide a valid token (previously produced by the sign-function), and also the same secret that was used to encrypt the data, the jwt.decode function will be able to reproduce the JSON Data that was initially encrypted.

What's pretty cool about this, is that you can handle sessions in a stateless manner. You can shut down your server and restart it or have 10 servers, as long as you have the same secret everywhere, all the servers will decode the same token into the same JSON Data.

Let's make an actual example.

  1. John logs in with john@gmail.com and his password - no JWT involved here.
  2. The backend checks if the password is correct
  3. If the password is correct, the backend fetches John from the database. Something like
    const john = {email: "john@gmail", firstName: "John", lastName: "Doe"}
    is returned from the database. Still no JWT.
  4. Now JWT enters the game. The backend applies jwt sign function: jwt.sign(john, secret) and attaches the generated token to the express response.
  5. The frontend stores the token, for example in local storage.
  6. On subsequent requests (which require authentication), the frontend attaches the token to the request, so the backend can identify the client.

The code to generate the token might look like this in Express:

app.post('/api/login', validatePayloadMiddleware, (req, res) => {
  const user = appUsers[req.body.email];
  if (user && user.pw === req.body.password) {
    const token = jwt.sign(user, serverJWT_Secret); // <==== The all-important "jwt.sign" function
    res.status(200).send({
      user: user,
      token: token
    });
  } else {
    res.status(403).send({
      errorMessage: 'Permission denied!'
    });
  }
});

The code to decrypt / decode the token might look like this:

const jwtMiddleware = (req, res, next) => {
  /**
   * In JWT it is convention that the token is provided to the server in the authorization header including a prefix,
   * separated by a space. The authorization header could be:
   * 'Token eyJhbGciOiJIUzI1NiIsInR...' or 'Bearer eyJhbGciOiJIUzI1NiIsInR...' or something like this.
   */
  const authString = req.headers['authorization'];
  if(typeof authString === 'string' && authString.indexOf(' ') > -1) {
    const authArray = authString.split(' ');
    const token = authArray[1];
    jwt.verify(token, serverJWT_Secret, (err, decoded) => {
      if(err) {
        res.sendStatus(403);
      } else {
        req.decoded = decoded;
        next();
      }
    });
  } else {
    res.sendStatus(403);
  }
};

The responsible frontend code will attach the authorization header:

import {Injectable} from '@angular/core';
import {HttpClient, HttpHeaders} from '@angular/common/http';
import {ToastrService} from 'ngx-toastr';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
import { environment } from '../environments/environment';

@Injectable()
export class AuthService {

  loggedIn: BehaviorSubject<boolean>

  getToken(): string {
    return window.localStorage['jwtToken'];
  }

  saveToken(token: string) {
    window.localStorage['jwtToken'] = token;
  }

  destroyToken() {
    window.localStorage.removeItem('jwtToken');
  }

  buildHeaders(): HttpHeaders {
    const headersConfig = {
      'Content-Type': 'application/json',
      'Accept': 'application/json'
    };

    if (this.getToken()) {
      headersConfig['Authorization'] = `Token ${this.getToken()}`;
    }
    return new HttpHeaders(headersConfig);
  }


  login(email: string, password: string) {
    this.http.post(environment.apiUrl + '/login', {
      email: email,
      password: password
    }).subscribe((resp: any) => {
      this.loggedIn.next(true);
      this.saveToken(resp.token);
      this.toastr.success(resp && resp.user && resp.user.name ? `Welcome ${resp.user.name}` : 'Logged in!');
    }, (errorResp) => {
      this.loggedIn.next(undefined);
      errorResp.error ? this.toastr.error(errorResp.error.errorMessage) : this.toastr.error('An unknown error has occured.');
    });
  }

  logout() {
    this.destroyToken();
    this.loggedIn.next(false);
  }

  constructor(
    private http: HttpClient,
    private toastr: ToastrService
  ) {
    const jwtToken = this.getToken();
    this.loggedIn = new BehaviorSubject<boolean>jwtToken ? true : false);
  }

}

Notes

There are some important sidenotes to be made, when you want to use JWT for your sessions. Not everyone thinks it's a good idea, mostly due to difficulties with invalidation of sessions. Here are two sources, that think it's a bad idea:

I can understand where they are coming from. If this has also convinced you to stick with traditional sessions, check out the other tutorial about regular sessions here: https://www.tsmean.com/articles/authentication/express-session-angular/

The full code can be found on Github: https://github.com/bersling/jwt-express-angular.

There's also a youtube video explaining the concepts. It goes into more detail than this article and actually builds a small example app with login / logout.

In any case, I hope we could bring the basics of authentication with JWT closer to you!

Dear Devs: You can help Ukraine🇺🇦. I opted for (a) this message and (b) a geo targeted message to Russians coming to this page. If you have a blog, you could do something similar, or you can link to a donations page. If you don't have one, you could think about launching a page with uncensored news and spread it on Russian forums or even Google Review. Or hack some russian servers. Get creative. #StandWithUkraine 🇺🇦
Dear russians🇷🇺. I am a peace loving person from Switzerland🇨🇭. It is without a doubt in my mind, that your president, Vladimir Putin, has started a war that causes death and suffering for Ukrainians🇺🇦 and Russians🇷🇺. Your media is full of lies. Lies about the casualties, about the intentions, about the "Nazi Regime" in Ukraine. Please help to mobilize your people against your leader. I know it's dangerous for you, but it must be done!