# Role Based JWT Authentication

Here you will find how Role Based JWT authentication works, which is provided by our template.

TIP

To implement JWT authentication in the starter-kit read article from here.

To implement Role Based authentication in the starter-kit read article from here.

# Folder structure

├── app
│   ├── auth                                -> Auth folder contains all files
│   │   ├── helpers                         -> Contains all helper files
│   │   │   ├── auth.guards.ts              -> Auth guard file
│   │   │   ├── error.interceptor.ts        -> Error inspector file
│   │   │   ├── fake-backend.ts             -> Fake backend file
│   │   │   ├── jwt.interceptor.ts          -> Jwt inspector file
│   │   │   └── index.ts                    -> Index file, helper files are imported
│   │   ├── service                         -> Service Folder
│   │   │   ├── authentication.service.ts   -> Auth service file
│   │   │   ├── user.service.ts             -> User service file to manage user
│   │   │   └── index.ts                    -> Index file, where all service files are imported
│   │   ├── models                          -> Model Folder for role & user management
│   │   │   ├── role.ts                     -> Role file to manage role
│   │   │   ├── user.ts                     -> User file (model for user properties)
│   │   │   └── index.ts                    -> Index file, where all model files are imported

# Auth

You can find our Role-Based JWT Auth files in auth folder, Path: starter-kit/src/app/auth.

Let's understand what each file has to offer:

# jwt.interceptor.ts

The JWT Interceptor intercepts HTTP requests from the application to add a JWT auth token to the Authorization header if the user is logged in and the request to the application API URL(environment.apiUrl).

It's implemented using the HttpInterceptor class included in the HttpClientModule, by extending the HttpInterceptor class you can create a custom interceptor to modify HTTP requests before they get sent to the server.

TIP

For more details about HttpInterceptor click here (opens new window).

NOTE

HTTP_INTERCEPTORS are added to the request pipeline in the providers array of the app.module.ts file.

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

import { environment } from 'environments/environment'
import { AuthenticationService } from 'app/auth/service'

@Injectable()
export class JwtInterceptor implements HttpInterceptor {
  /**
   *
   * @param {AuthenticationService} _authenticationService
   */
  constructor(private _authenticationService: AuthenticationService) {}

  /**
   * Add auth header with jwt if user is logged in and request is to api url
   * @param request
   * @param next
   */
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const currentUser = this._authenticationService.currentUserValue
    const isLoggedIn = currentUser && currentUser.token
    const isApiUrl = request.url.startsWith(environment.apiUrl)
    if (isLoggedIn && isApiUrl) {
      request = request.clone({
        setHeaders: {
          Authorization: `Bearer ${currentUser.token}`
        }
      })
    }

    return next.handle(request)
  }
}

# fake-backend.ts

TIP

You can modify fake users in this file.

WARNING

As we are using fake backend we are not generating a real JWT token.

A fake backend that intercepts the HTTP requests from the Angular app and sends back "fake" responses. This is done by a class that implements the Angular HttpInterceptor interface, for more information on Angular HTTP Interceptors (opens new window)

The fake backend contains a handleRoute() function that checks if the request matches one of the faked routes in the switch statement, at the moment this includes POST requests to the /users/authenticate route for handling authentication, and GET requests to the /users route for getting all users.

Requests to the authenticate route are handled by the authenticate() function which checks the email and password against an array of hardcoded users. If the email and password are correct then an ok response is returned with the user details and a fake jwt token, otherwise, an error response is returned.

Requests to the get users route are handled by the getUsers() function which checks if the user is logged in by calling the new isLoggedIn() helper function. If the user is logged in an ok() response with the whole users array is returned, otherwise, a 401 Unauthorized response is returned by calling the new unauthorized() helper function.

If the request doesn't match any of the faked routes it is passed through as a real HTTP request to the backend API.

/**
 *  ? Tip:
 *
 * For Actual Node.js - Role Based Authorization Tutorial with Example API
 * Refer: https://jasonwatmore.com/post/2018/11/28/nodejs-role-based-authorization-tutorial-with-example-api
 * Running an Angular 9 client app with the Node.js Role Based Auth API
 */

import { Injectable } from '@angular/core'
import {
  HttpRequest,
  HttpResponse,
  HttpHandler,
  HttpEvent,
  HttpInterceptor,
  HTTP_INTERCEPTORS
} from '@angular/common/http'
import { Observable, of, throwError } from 'rxjs'
import { delay, mergeMap, materialize, dematerialize } from 'rxjs/operators'

import { User, Role } from 'app/auth/models'

// Users with role
const users: User[] = [
  {
    id: 1,
    email: 'admin@demo.com',
    password: 'admin',
    firstName: 'John',
    lastName: 'Doe',
    avatar: 'avatar-s-11.jpg',
    role: Role.Admin
  },
  {
    id: 2,
    email: 'client@demo.com',
    password: 'client',
    firstName: 'Nataly',
    lastName: 'Doe',
    avatar: 'avatar-s-2.jpg',
    role: Role.Client
  },
  {
    id: 3,
    email: 'user@demo.com',
    password: 'user',
    firstName: 'Rose',
    lastName: 'Doe',
    avatar: 'avatar-s-3.jpg',
    role: Role.User
  }
]

@Injectable()
export class FakeBackendInterceptor implements HttpInterceptor {
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const { url, method, headers, body } = request

    // wrap in delayed observable to simulate server api call
    return of(null).pipe(mergeMap(handleRoute))
    // .pipe(materialize()) // call materialize and dematerialize to ensure delay even if an error is thrown (https://github.com/Reactive-Extensions/RxJS/issues/648)
    // .pipe(delay(500))
    // .pipe(dematerialize());

    function handleRoute() {
      switch (true) {
        case url.endsWith('/users/authenticate') && method === 'POST':
          return authenticate()
        case url.endsWith('/users') && method === 'GET':
          return getUsers()
        case url.match(/\/users\/\d+$/) && method === 'GET':
          return getUserById()
        default:
          // pass through any requests not handled above
          return next.handle(request)
      }
    }

    // route functions

    function authenticate() {
      const { email, password } = body
      const user = users.find(x => x.email === email && x.password === password)
      if (!user) return error('Username or password is incorrect')
      return ok({
        id: user.id,
        email: user.email,
        firstName: user.firstName,
        lastName: user.lastName,
        avatar: user.avatar,
        role: user.role,
        token: `fake-jwt-token.${user.id}`
      })
    }

    function getUsers() {
      if (!isAdmin()) return unauthorized()
      return ok(users)
    }

    function getUserById() {
      if (!isLoggedIn()) return unauthorized()

      // only admins can access other user records
      if (!isAdmin() && currentUser().id !== idFromUrl()) return unauthorized()

      const user = users.find(x => x.id === idFromUrl())
      return ok(user)
    }

    // helper functions

    function ok(body) {
      return of(new HttpResponse({ status: 200, body }))
    }

    function unauthorized() {
      return throwError({ status: 401, error: { message: 'unauthorized' } })
    }

    function error(message) {
      return throwError({ status: 400, error: { message } })
    }

    function isLoggedIn() {
      const authHeader = headers.get('Authorization') || ''
      return authHeader.startsWith('Bearer fake-jwt-token')
    }

    function isAdmin() {
      return isLoggedIn() && currentUser().role === Role.Admin
    }

    function currentUser() {
      if (!isLoggedIn()) return
      const id = parseInt(headers.get('Authorization').split('.')[1])
      return users.find(x => x.id === id)
    }

    function idFromUrl() {
      const urlParts = url.split('/')
      return parseInt(urlParts[urlParts.length - 1])
    }
  }
}

export const fakeBackendProvider = {
  // use fake backend in place of Http service for backend-less development
  provide: HTTP_INTERCEPTORS,
  useClass: FakeBackendInterceptor,
  multi: true
}

# error.interceptor.ts

The Error Interceptor intercepts HTTP responses from the API to check if there were any errors.

NOTE

If there is a 401 or 403 Unauthorized response the user will be redirected to the not-authorized page.

For all other errors, it will appear in the console of the browser.

It's implemented using the HttpInterceptor class included in the HttpClientModule, by extending the HttpInterceptor class you can create a custom interceptor to catch all error responses from the server in a single location.

import { Injectable } from '@angular/core'
import { Router } from '@angular/router'
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http'
import { Observable, throwError } from 'rxjs'
import { catchError } from 'rxjs/operators'

import { AuthenticationService } from 'app/auth/service'

@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
  /**
   * @param {Router} _router
   * @param {AuthenticationService} _authenticationService
   */
  constructor(private _router: Router, private _authenticationService: AuthenticationService) {}

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(request).pipe(
      catchError(err => {
        if ([401, 403].indexOf(err.status) !== -1) {
          // auto logout if 401 Unauthorized or 403 Forbidden response returned from api
          this._router.navigate(['/pages/miscellaneous/not-authorized'])

          // ? Can also logout and reload if needed
          // this._authenticationService.logout();
          // location.reload(true);
        }
        // throwError
        const error = err.error.message || err.statusText
        return throwError(error)
      })
    )
  }
}

# auth.guards.ts

The auth guard is an angular route guard that's used to prevent unauthenticated users from accessing restricted routes, it does this by implementing the CanActivate interface which allows the guard to decide if a route can be activated with the canActivate() method. If the method returns true the route is activated (allowed to proceed), otherwise if the method returns false the route is blocked.

TIP

For more information about CanActivate click here (opens new window)

The auth guard uses the authentication service to check if the user is logged in, if they are logged in it returns true from the canActivate() method, otherwise, it returns false and redirects the user to the login page.

import { Injectable } from '@angular/core'
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'

import { AuthenticationService } from 'app/auth/service'

@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
  /**
   *
   * @param {Router} _router
   * @param {AuthenticationService} _authenticationService
   */
  constructor(private _router: Router, private _authenticationService: AuthenticationService) {}

  // canActivate
  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
    const currentUser = this._authenticationService.currentUserValue

    if (currentUser) {
      // check if route is restricted by role
      if (route.data.roles && route.data.roles.indexOf(currentUser.role) === -1) {
        // role not authorised so redirect to not-authorized page
        this._router.navigate(['/pages/miscellaneous/not-authorized'])
        return false
      }

      // authorised so return true
      return true
    }

    // not logged in so redirect to login page with the return url
    this._router.navigate(['/pages/authentication/login-v2'], { queryParams: { returnUrl: state.url } })
    return false
  }
}

# Service

# Authentication Service

Path: starter-kit/src/app/auth/service/authentication.service.ts

The authentication service is used to login & logout of the Angular app, it notifies other components when the user logs in & out and allows access to the currently logged in user.

RxJS Subjects and Observables are used to store the current user object and notify other components when the user logs in and out of the app. Angular components can subscribe() to the public currentUser: Observable property to be notified of changes, and notifications are sent when the this.currentUserSubject.next() method is called in the login() and logout() methods, passing the argument to each subscriber. The RxJS BehaviorSubject is a special type of Subject that keeps hold of the current value and emits it to any new subscribers as soon as they subscribe, while regular Subjects don't store the current value and only emit values that are published after a subscription is created.

The login() method sends the user credentials to the API via an HTTP POST request for authentication. If successful the user object including a JWT auth token are stored in localStorage to keep the user logged in between page refreshes. The user object is then published to all subscribers with the call to this.currentUserSubject.next(user);.

The constructor() of the service initializes the currentUserSubject with the currentUser object from localStorage which enables the user to stay logged in between page refreshes or after the browser is closed. The public currentUser property is then set to this.currentUserSubject.asObservable(); which allows other components to subscribe to the currentUser Observable but doesn't allow them to publish to the currentUserSubject, this is so logging in and out of the app can only be done via the authentication service.

The currentUserValue getter allows other components an easy way to get the value of the currently logged in user without having to subscribe to the currentUser Observable.

The logout() method removes the current user object from local storage and publishes null to the currentUserSubject to notify all subscribers that the user has logged out.

import { Injectable } from '@angular/core'
import { HttpClient } from '@angular/common/http'
import { BehaviorSubject, Observable } from 'rxjs'
import { map } from 'rxjs/operators'

import { environment } from 'environments/environment'
import { User, Role } from 'app/auth/models'
import { ToastrService } from 'ngx-toastr'

@Injectable({ providedIn: 'root' })
export class AuthenticationService {
  //public
  public currentUser: Observable<User>

  //private
  private currentUserSubject: BehaviorSubject<User>

  /**
   *
   * @param {HttpClient} _http
   * @param {ToastrService} _toastrService
   */
  constructor(private _http: HttpClient, private _toastrService: ToastrService) {
    this.currentUserSubject = new BehaviorSubject<User>(JSON.parse(localStorage.getItem('currentUser')))
    this.currentUser = this.currentUserSubject.asObservable()
  }

  // getter: currentUserValue
  public get currentUserValue(): User {
    return this.currentUserSubject.value
  }

  /**
   *  Confirms if user is admin
   */
  get isAdmin() {
    return this.currentUser && this.currentUserSubject.value.role === Role.Admin
  }

  /**
   *  Confirms if user is client
   */
  get isClient() {
    return this.currentUser && this.currentUserSubject.value.role === Role.Client
  }

  /**
   * User login
   *
   * @param email
   * @param password
   * @returns user
   */
  login(email: string, password: string) {
    return this._http
      .post<any>(`${environment.apiUrl}/users/authenticate`, { email, password })
      .pipe(
        map(user => {
          // login successful if there's a jwt token in the response
          if (user && user.token) {
            // store user details and jwt token in local storage to keep user logged in between page refreshes
            localStorage.setItem('currentUser', JSON.stringify(user))

            // Display welcome toast!
            setTimeout(() => {
              this._toastrService.success(
                'You have successfully logged in as an ' +
                  user.role +
                  ' user to Vuexy. Now you can start to explore. Enjoy! 🎉',
                '👋 Welcome, ' + user.firstName + '!',
                { toastClass: 'toast ngx-toastr', closeButton: true }
              )
            }, 2500)

            // notify
            this.currentUserSubject.next(user)
          }

          return user
        })
      )
  }

  /**
   * User logout
   *
   */
  logout() {
    // remove user from local storage to log user out
    localStorage.removeItem('currentUser')
    // notify
    this.currentUserSubject.next(null)
  }
}

# User Service

Path: starter-kit/src/app/auth/service/user.service.ts

The user service contains a method for getting all users from the API, I included it to demonstrate accessing a secure API endpoint with the http authorization header set after logging in to the application, the auth header is set with a JWT token with the JWT Interceptor above. The secure endpoint in the example is a fake one implemented in the fake backend provider above.

import { Injectable } from '@angular/core'
import { HttpClient } from '@angular/common/http'

import { environment } from 'environments/environment'
import { User } from 'app/auth/models'

@Injectable({ providedIn: 'root' })
export class UserService {
  /**
   *
   * @param {HttpClient} _http
   */
  constructor(private _http: HttpClient) {}

  /**
   * Get all users
   */
  getAll() {
    return this._http.get<User[]>(`${environment.apiUrl}/users`)
  }

  /**
   * Get user by id
   */
  getById(id: number) {
    return this._http.get<User>(`${environment.apiUrl}/users/${id}`)
  }
}

# Models

# role.ts

Path : starter-kit/src/app/auth/models/role.ts

The role model is a enum that defines the all possible roles of a user.

TIP

You can add / remove roles as per preferences, Example : For a visitor role you can add Visitor='Visitor'

export enum Role {
  Admin = 'Admin',
  Client = 'Client',
  User = 'User'
}

# user.ts

Path : starter-kit/src/app/auth/models/user.ts

The user model is a class that defines the properties of a user.

import { Role } from './role'

export class User {
  id: number
  email: string
  password: string
  firstName: string
  lastName: string
  avatar: string
  role: Role
  token?: string
}
Last Updated: 3/11/2021, 8:16:33 PM