How to Implement Role-Based Access Control with JWT-Based Authorization in Angular

How to Implement Role-Based Access Control with JWT-Based Authorization in Angular

As web applications become more complex and user data is increasingly sensitive, it is essential to implement proper access control mechanisms to secure the application resources.

One effective method for implementing access control is Role-Based Access Control (RBAC).

In this blog post, we will show you how to implement RBAC with JSON Web Token (JWT) based authorization in an Angular application.

Step 1: Setup Angular Application

First, we need to set up an Angular application,Open your terminal and run the following command:

ng new angular-rbac-jwt

Step 2: Create User and Role Interfaces

Next, we need to define user and role interfaces. In this example, we will create two interfaces in a new file named user.ts:

export interface User {
  id: number;
  username: string;
  password: string;
  role: Role;
}

export interface Role {
  id: number;
  name: string;
  permissions: string[];
}

We define a user interface with an id, username, password, and role. The role property is an object of type Role that has an id, name, and permissions array.

Step 3: Create JWT Service

To handle JWT-based authentication and authorization, we need to create a service. Create a new file named auth.service.ts and add the following code:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { User } from './user';

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  private apiUrl = 'http://localhost:3000/api/auth';

  constructor(private http: HttpClient) { }

  login(username: string, password: string): Observable<any> {
    return this.http.post<any>(`${this.apiUrl}/login`, { username, password })
      .pipe(
        tap(res => {
          localStorage.setItem('access_token', res.access_token);
        })
      );
  }

  logout() {
    localStorage.removeItem('access_token');
  }

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

  get currentUser(): User {
    const token = localStorage.getItem('access_token');
    if (!token) {
      return null;
    }
    const payload = JSON.parse(atob(token.split('.')[1]));
    return payload.user;
  }

  public hasPermission(permission: string): boolean {
    const user = this.currentUser;
    if (!user || !user.role || !user.role.permissions) {
      return false;
    }
    return user.role.permissions.includes(permission);
  }
}

We define an AuthService class with a login() method that sends a POST request to our API with the username and password. If the API returns an access token, we store it in local storage using the tap() operator. We also define a logout() method to remove the access token from local storage.

The loggedIn property returns a boolean indicating whether the user is currently authenticated based on the presence of the access token in local storage. The currentUser property returns the user object stored in the access token's payload. Finally, the hasPermission() method takes a permission string and checks if the current user has that permission based on their role.

Step 4: Implement Role-Based Access Control

To implement role-based access control, we will create a guard that checks if the user has the required permission to access a route. Create a new file named role.guard.ts and add the following code:

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { AuthService } from './auth.service';

@Injectable({
  providedIn: 'root'
})
export class RoleGuard implements CanActivate {

  constructor(private authService: AuthService, private router: Router) {}

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    const requiredPermission = route.data.requiredPermission;
    if (this.authService.hasPermission(requiredPermission)) {
      return true;
    }
    this.router.navigate(['/login']);
    return false;
  }
}

The RoleGuard class implements the CanActivate interface and defines a canActivate() method. This method checks if the current user has the required permission to access the route. If the user has the required permission, the method returns true, allowing the user to access the route. If the user does not have the required permission, the method redirects the user to the login page and returns false.

Step 5: Use the RoleGuard

Now that we have defined the RoleGuard, we can use it to protect routes that require specific permissions. For example, suppose we have a route that allows only users with the viewReports permission to access. We can protect this route by adding the RoleGuard to the route's canActivate property and passing the required permission as data. Here's an example:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { LoginComponent } from './login/login.component';
import { ReportsComponent } from './reports/reports.component';
import { RoleGuard } from './role.guard';

const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'login', component: LoginComponent },
  { path: 'reports', component: ReportsComponent, canActivate: [RoleGuard], data: { requiredPermission: 'viewReports' } }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

In the above code, we add the RoleGuard to the canActivate property of the ReportsComponent route and pass the viewReports permission as data.

Step 6: Test the Application

To test the application, we need to create a mock API that returns an access token with a user object containing a role with permissions. For this example, we will use json-server to create a mock API.

Create a db.json file with the following code:

{
  "users": [
    {
      "id": 1,
      "username": "admin",
      "password": "admin",
      "role": {
        "id": 1,
        "name": "Administrator",
        "permissions": ["viewReports", "editReports", "deleteReports"]
      }
    },
    {
      "id": 2,
      "username": "user",
      "password": "user",
      "role": {
        "id": 2,
        "name": "User",
        "permissions": ["viewReports"]
      }
    }
  ]
}

This file defines two users with different roles and permissions.

To start the mock API, run the following command in your terminal:

json-server --watch db.json

This will start a mock API server at http://localhost:3000.

Now we can test the application by logging in with the admin user and trying to access the /reports route. The admin user has the viewReports, editReports, and deleteReports permissions, so they should be able to access the /reports route.

To log in, enter the admin username and password on the login page. Once logged in, click on the "Reports" link in the navigation menu. This should take you to the ReportsComponent.

To test if the RoleGuard is working, add the canActivate property to the HomeComponent route and set it to the RoleGuard with the viewReports permission. This will prevent users without the viewReports permission from accessing the home page.

Here's an example of how to protect the home page with the RoleGuard:

{ path: '', component: HomeComponent, canActivate: [RoleGuard], data: { requiredPermission: 'viewReports' } },

Now, if you log in with the user username and password, which only has the viewReports permission, you should be redirected to the login page when trying to access the home page.

Congratulations! You have successfully implemented role-based access control with JWT-based authorization in Angular.

Subscribe to our Newsletter

Stay up to date! Get all the latest posts delivered straight to your inbox.

If You Appreciate What We Do Here On TutsCoder, You Should Consider:

If you like what you are reading, please consider buying us a coffee ( or 2 ) as a token of appreciation.

Support Us

We are thankful for your never ending support.

Leave a Comment