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.