Brave publisher PUBLISHER
Web developer Front-End in Oise, France - Gary Deshayes

Angular, ExpressJS | JWT authentication with Angular 10 and ExpressJS 4 (MySQL)

Web developer Front-End in Oise, France - Gary Deshayes <--Traduction

Created at : Sunday 20 September 2020

I will assume that you already have your Front with Angular 10 and your Back with ExpressJS 4 and I will not show the creation part of these two projects.

Creating the login form under Angular


The back side of the form :


Go to your form.component.ts file (Make it according to your components...) and we will build it as on the example :

import { Component, OnInit } from '@angular/core';
// Import of FormBuilder to build the form
import { FormBuilder } from '@angular/forms';

@Component({
  selector: 'app-login-form',
  templateUrl: './login-form.component.html',
  styleUrls: ['./login-form.component.scss']
})

export class LoginFormComponent implements OnInit {
  // required field
  email: String = '';
  password: String = '';
  // our form
  loginForm;

  constructor(private formBuilder: FormBuilder) {
    // During the creation of the component we define the form, without validation, the tutorial 
    will not deal with this part.
    this.loginForm = this.formBuilder.group({
      email: '',
      password: ''
    });
  }

  ngOnInit(): void {}
}

We see that we import the form builder which is none other than the FormBuilder, we instantiate our form variables and define the basis of the form from the FormBuilder.

The front side of the form :

<div id="login">
    <h1>Login form</h1>
    // The notion formGroup defines to which variable the form is built in the back side of the 
    form.
    <form [formGroup]="loginForm">
        <div class="form-input">
            <label for="email">Email</label>
            // The formControlName concept allows us to tell our formBuilder to which variable the input data is linked.
            // The tag [(ngModel)]="email" allows, when modifying the email in the input, to update the data of the variable
            <input type="text" formControlName="email" [(ngModel)]="email">
        </div>
        <div class="form-input">
            // See email these are the same explanations
            <label for="password">Password</label>
            <input type="password" formControlName="password" [(ngModel)]="password">
        </div>
        // The (click)="submit" indicates that when the button is clicked, the submit() function will be launched.
        <button type="button" (click)="submit()">Submit</button>
    </form>
</div>

We create a basic form with the directives formGroup, formControlName and ngModel which are new and explained in the code, we will continue and create the login service that will interact with the ExpressJS server.

Login service and server submission function:


Before writing the function of submitting the login to the server, you must write a login service. This will allow us if we want to reuse it in another place of our Angular components.

import { Injectable } from '@angular/core';
// To send HTTP request
import { HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})
export class LoginService {
  // User will be stocked here
  currentUser;
  // The jwt token here
  token;
  // the url to the api, don't forget the http:// /!\
  urlApi = 'http://localhost:3000/api/';

  constructor(private http: HttpClient) { }

  login(data) {
    // This function will be called in our form submission, it allows to call the api in post on the road 'http://localhost:3000/api/login', returning this.http.post will return an Observable that can be subscribed to receive the data in the form.
    return this.http.post(this.urlApi + 'login', data);
  }
}

Now that we have our service which is reusable, don't forget to import it in our form.component.ts to be able to call it.

Now it's time to create the submission function.

import { Component, OnInit } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { LoginService } from '../services/login-service.service'

@Component({
  selector: 'app-login-form',
  templateUrl: './login-form.component.html',
  styleUrls: ['./login-form.component.scss']
})
export class LoginFormComponent implements OnInit {
  email: String = '';
  password: String = '';
  loginForm;
  currentUser;

  // We call the loginService to use its login function.
  constructor(private formBuilder: FormBuilder, private loginService: LoginService) {
    
    this.loginForm = this.formBuilder.group({
      email: '',
      password: ''
    });
  }

  ngOnInit(): void {}

  // Submit the form
  submit(){
    // We create a data object with the fields of the completed form
    let data = { email: this.email, password: this.password }
    // We call the login service on the login method with the object data
    // The login service will send the HTTP request as a post to the server with the user's data.
    // We apply the subscribe method, which will listen to the returned observable.
    // The first function (value) is the return of the value, there is also the second (err) which allows to manage the error and the third () => {} which allows to define code when the subscriber is finished.
    this.loginService.login(data).subscribe(
      (value) => {
        this.loginService.currentUser = value
        this.loginService.token = value.token,
        this.currentUser = value
        localStorage.setItem('token', value.token)
      },
      (err) => console.log(err)
    )
  }

}

If you are not familiar with the Observables you can go to the official Angular documentation which will explain it to you.

Now that we have our form built, a service that queries the server, and a subscriber that returns the data from the server, we can now work on the server itself.

ExpressJS server


As for the Angular front, I'm going to get to the heart of the matter right away, if you need to know more about ExpressJS, go to the site directly.

To make our authentication work we need the mysql library and the JWT library to generate a token. I let you install them to continue.

Cross Origin requests can also be annoying, install CORS

MySQL database service


Here is the DB service to put in a separate file to call the database easily.

var mysql      = require('mysql');
var connection = mysql.createConnection({
  host     : 'localhost',
  user     : 'root',
  password : '',
  database : 'angular-express'
});
 
connection.connect(function(err) {
    if (err) throw err;
});

module.exports = connection;

Replace with your data.

The server

const express = require('express')
// Allow Cross Origin
const cors = require("cors");
const app = express()
const port = 3000
// Allows to convert data received in HTTP into readable variable
const bodyParser = require('body-parser');
//Allows you to use the database, change according to you
var db = require('./database');
// To use JWT
var jwt = require('jsonwebtoken');

// We convert the data
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

// We allow cross origin (Angular in port 4200 and api in 3000 ...)
app.use(cors());

// Our login route
app.post('/api/login', (req, res) => {
  // We retrieve the variables sent by our form from the login service.
  let { email, password } = req.body;
  // We instantiate a variable that greets the user
  let user;
  // We make our request to verify that the user exists under the email sent.
  db.query('SELECT * FROM user WHERE email = ?', [email], function(err, result) {
    if (err) throw err;
    // We store our user in our variable
    user = result[0]
    // Small parenthesis, for this tuto the mdp are not hashed in the base, it is not the subject, then we compare without decrypting the passwords
    if(user.password == password){
      // If all the information is ok then we generate the jwt with the user's data (we avoid to put the password it's not secured), and we add a 'secret' keyword, to change according to your wishes, it must be secret precisely, we define the jwt valid one hour
      var token = jwt.sign({ id: user.id, email: user.email, name: user.name }, 'secret', { expiresIn: '1h' });
      // We give the token to the user and we return the user
      user.token = token
      res.send(user)
    } else {
      // Otherwise the user is warned that no one is found.
      res.send(["User don't find"])
    }
  });
})

// The server listen
app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})

All is good, for my part here are my test data.

Angular 10 and ExpressJS authentication, data on database

My form (a bit stylized) :

Angular 10 and ExpressJS authentication, login form

To check the status of the request returned by your server, open the developer console of your browser (Ctrl SHIFT + I under chrome) and go to network.

Submit the form and you will get a preview of the request sent.

Angular 10 and ExpressJS authentication, check request status

We see to which URL the request was sent, the method, and the status. Go to the Preview tab to see the data returned by ExpressJS.

Angular 10 et ExpressJS l'authentication, HTTP Request Preview

We have received our user's data with this famous JWT token, which we can decrypt on the JWT.io site.

Angular 10 et ExpressJS l'authentication, JWT.io

Hence the interest of not storing the password in the JWT! We also have the iat data for when the token was created and exp for its expiration.

Our form is now able to receive the user who wants to log in, give him a token that proves that the user has entered his credentials, and the login service has stored all this in memory to be reused wherever we want, now it's time to see the secure api routes via the token.

Secure routes via JWT on ExpressJS


Let's take this road as an example:

app.get('/api/dataNeedLogged', (req, res) => {
  res.send({ message: "You are logged with JWT you can see the data"})
})

It sends us a simple message but the a is not the subject, it has nothing different from the login route for the moment, we will secure it. It is necessary to set up a JWT authentication middleware. If I had to explain roughly what the Middleware is, it's like a function that launches before your main function, see example :

//We import the middleware
let authMiddleware = require('./auth_jwt.middleware')

//We add our middleware BEFORE the main function of our route
app.get('/api/dataNeedLogged', authMiddleware, (req, res) => {
  res.send({ message: "You are logged with JWT you can see the data"})
})

Middleware content :

//Dependancies imports
const jwt = require("jsonwebtoken")
const db = require('./database')

//Here is the function that will be launched before the main function of the route
//It has the same parameters as our route to an EXCEPTION, the NEXT parameter, it is mandatory and allows you to tell the MIDDLEWARE ok you can go to the main function
const auth = (req, res, next) => {
    try {
        // We retrieve the token of the HTTP request
        const token = req.header('Authorization').replace("Bearer ", "")
        // Decode the token with the "secret" key, replace with your secret key!
        const decoded = jwt.verify(token, "secret")

        //Thanks to the decoded token we have the id of the user, so we can retrieve it.
        //and judge whether or not he or she has the right of access
        db.query('SELECT * FROM user WHERE id = ?', [decoded.id], function (err, result) {
            if (err) throw err;
            user = result[0]
            //If no user is found, error 401 is issued.
            if (!user) {
                throw new Error()
            }
            req.token = token
            req.user = user
            //We move on to the main query
            next()
        });
        
    } catch (e) {
        res.status(401).send({ error: 'Please authenticate.' })
    }
}

//We export the function to include it before our route function.
module.exports = auth;

Angular must now send us the authentication token for each request, this is managed with an interceptor.

Angular, add the JWT token to the HTTP request


Here is the interceptor class that will allow us to add the token to our HTTP requests:

import {Injectable} from '@angular/core';
import {
  HttpEvent,
  HttpInterceptor,
  HttpHandler,
  HttpRequest
} from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  constructor() {}

  //Before sending an HTTP request, the interceptor will add the token to the authentication.
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    //Token retrieval
    const token = localStorage.getItem('token');
    //We clone the query by adding a headers Authorization with the Bearer string 
    followed by the token
    req = req.clone({
      setHeaders: {
        'Authorization': `Bearer ${token}`
      },
    });

    return next.handle(req);
  }
}

In order for it to intercept all the requests it is necessary to go to app.module.ts and add the following provider:

//We import HTTP_INTERCEPTORS
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
//and our interceptor
import { AuthInterceptor } from './interceptor/auth.interceptor'

...
...
...
//And we tell Angular to use it...
providers: [
    {provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true},
],

Now our requests from Angular are ready to communicate with the secure routes of the ExpressJS API, the tutorial is now over.

Thanks for reading, feel free to share!

News publications :

Symfony 5 | Return an image via a controller route

Symfony

Created at : Wednesday 1 September 2021

VueJS 3, ExpressJS 4.17 | Uploading and resizing an image

Javascript Framework JS

Created at : Saturday 28 November 2020

Symfony Event Subscriber | Restricting an ip range with an Event Subscriber

Symfony

Created at : Saturday 14 November 2020

Angular, ExpressJS | JWT authentication with Angular 10 and ExpressJS 4 (MySQL)

Javascript SQL Framework JS

Created at : Sunday 20 September 2020

Symfony, Excel, CSV | Generating a CSV file for Excel with Symfony

Symfony

Created at : Thursday 6 August 2020

Symfony FormType Choice | Set data default to group of radio button

Symfony

Created at : Sunday 26 July 2020

Doctrine, Symfony | Difference between two dates in MySQL (Day, month, year...)

Symfony SQL

Created at : Thursday 18 June 2020

JavaScript, jQuery et Regex | Secure a password in real time with JS

Javascript jQuery

Created at : Monday 23 March 2020

Symfony, Doctrine | Retrieve old data of FormType

Symfony

Created at : Friday 14 February 2020

jQuery, Webpack and Symfony | Call jQuery in your twig files

Symfony Bugs

Created at : Sunday 26 January 2020