In the system’s security, the role-based authorization / role-based access control (RBAC) is a way of restricting and managing machine access to authorized users.
RBAC (Role-based access control) confines access to the network based on the employee’s role in an organization.
We will make an angular app to restrict the access of sources of a page by implementing role-based authorization. This application will contain 3 pages (a login page, a home page, an admin page). In our application, the user can either be a normal user or an admin. A normal user has only access to the home page and an admin can access the home page as well as the admin page.
Start by Creating y our application and select whatever style property you want by using this command in the terminal.
ng new role-based-authorization –-routing
Now, we will create folders for each module i.e., home, admin, login. And additional folders for helpers, services and models with prefix underscore (_) to differentiate them from other components.
We have made the following 3 components in the application:
Admin.component.html:
This file is an admin component template that contains html code for displaying content on the admin page.
Admin dashboard!!
All users from secure (admin only) api end point:
admin.component.ts:
The admin component calls the user service and finds all users from one secure API endpoint. It stores them in a local user’s property which is accessible to the admin component template.
import { Component, OnInit } from '@angular/core'; import { first } from 'rxjs/operators'; import { User } from '../_models'; import { UserService } from '../_services'; @Component({ templateUrl: 'admin.component.html' }) export class AdminComponent implements OnInit { loading = false; users: User[] = []; constructor(private userService: UserService) { } ngOnInit() { this.loading = true; this.userService.getAll().pipe(first()).subscribe(users => { this.loading = false; this.users = users; }); } }
home.component.html:
This file is a home component template which contains html code for displaying content on the home page.
Welcome to home page!!
Your role is: {{currentUser.role}}.
home.component.ts:
The home component fetches the current user from the authentication service so gets the current user from the api with the user service.
import { Component, OnInit } from '@angular/core'; import { Router, ActivatedRoute } from '@angular/router'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { first } from 'rxjs/operators'; import { AuthenticationService } from '../_services'; @Component({ templateUrl: 'login.component.html' }) export class LoginComponent implements OnInit { loginForm: FormGroup; loading = false; submitted = false; returnUrl: string; error = ''; constructor( private formBuilder: FormBuilder, private route: ActivatedRoute, private router: Router, private authenticationService: AuthenticationService ) { if (this.authenticationService.currentUserValue) { this.router.navigate(['/']); } } ngOnInit() { this.loginForm = this.formBuilder.group({ username: ['', Validators.required], password: ['', Validators.required] }); this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/'; } get f() { return this.loginForm.controls; } onSubmit() { this.submitted = true; if (this.loginForm.invalid) { return; } this.loading = true; this.authenticationService.login(this.f.username.value, this.f.password.value) .pipe(first()) .subscribe( data => { this.router.navigate([this.returnUrl]); }, error => { this.error = error; this.loading = false; }); } }
login.component.html
The home component fetches the current user from the authentication service and then gets the current user from the api with the user service.
login.component.ts:
This component makes use of the authentication service for logging into the application. In the case where the user is already logged in, it will automatically redirect them to the home page.
import { Component, OnInit } from '@angular/core'; import { Router, ActivatedRoute } from '@angular/router'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { first } from 'rxjs/operators'; import { AuthenticationService } from '../_services'; @Component({ templateUrl: 'login.component.html' }) export class LoginComponent implements OnInit { loginForm: FormGroup; loading = false; submitted = false; returnUrl: string; error = ''; constructor( private formBuilder: FormBuilder, private route: ActivatedRoute, private router: Router, private authenticationService: AuthenticationService ) { if (this.authenticationService.currentUserValue) { this.router.navigate(['/']); } } ngOnInit() { this.loginForm = this.formBuilder.group({ username: ['', Validators.required], password: ['', Validators.required] }); this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/'; } get f() { return this.loginForm.controls; } onSubmit() { this.submitted = true; if (this.loginForm.invalid) { return; } this.loading = true; this.authenticationService.login(this.f.username.value, this.f.password.value) .pipe(first()) .subscribe( data => { this.router.navigate([this.returnUrl]); }, error => { this.error = error; this.loading = false; }); } }
The followings are the files under the “_helper” folders.
The auth guard is an angular route guard which is used to shut out unauthenticated or unauthorized users from accessing restricted routes.
This is done by implementing the CanActivate interface that permits the guard to determine if a route is activated with the canActivate(). If this method returns true it means that the route is activated, the user is allowed to proceed, and if this method returns false the route is blocked.
The auth guard uses the authentication service to visualize if the user is logged in, if they’re logged in, it checks if their role is permitted to access the requested route. If they’re logged in and licensed the canActivate() method returns true, otherwise, it returns false and redirects the user to the login page.
Angular route guards are associated with the routes in the router config, this auth guard is used in app.routing.ts to protect the home page and admin page routes.
auth.guard.ts
import { Injectable } from '@angular/core'; import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; import { AuthenticationService } from '../_services' @Injectable({ providedIn: 'root' }) export class AuthGuard implements CanActivate { constructor( private router: Router, private authenticationService: AuthenticationService ) { } canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { const currentUser = this.authenticationService.currentUserValue; if (currentUser) { if (route.data.roles && route.data.roles.indexOf(currentUser.role) === -1) { this.router.navigate(['/']); return false; } return true; } this.router.navigate(['/login'], { queryParams: { returnUrl: state.url } }); return false; } }
HTTP Error Interceptor
The Error Interceptor intercepts HTTP responses from the api to check if there have been any errors. If there’s a 401 Unauthorized or 403 Forbidden response the user is automatically logged out of the application, all other errors will be re-thrown up to the calling service so that an alert with the error can be displayed on the screen.
This interceptor is applied using the HttpInterceptor class contained in the HttpClientModule.
Error.interceptor.ts:
import { Injectable } from '@angular/core'; import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http'; import { Observable, throwError } from 'rxjs'; import { catchError } from 'rxjs/operators'; import { AuthenticationService } from '../_services'; @Injectable() export class ErrorInterceptor implements HttpInterceptor { constructor(private authenticationService: AuthenticationService) { } intercept(request: HttpRequest, next: HttpHandler): Observable > { return next.handle(request).pipe(catchError(err => { if ([401, 403].indexOf(err.status) !== -1) { this.authenticationService.logout(); location.reload(true); } const error = err.error.message || err.statusText; return throwError(error); })) } }
In this application, we used a fake backend API to intercept the HTTP requests to run and test the angular application instead of using a real backend API. We have done this by a class that implements the Angular HttpInterceptor interface.
fake-backend.ts
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 '../_models'; const users: User[] = [ { id: 1, username: 'admin', password: 'admin', firstName: 'Admin', lastName: 'User', role: Role.Admin }, { id: 2, username: 'user', password: user', firstName: 'Normal', lastName: 'User', role: Role.User } ]; @Injectable() export class FakeBackendInterceptor implements HttpInterceptor { intercept(request: HttpRequest , next: HttpHandler): Observable > { const { url, method, headers, body } = request; return of(null) .pipe(mergeMap(handleRoute)) .pipe(materialize()) .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: return next.handle(request); } } function authenticate() { const { username, password } = body; const user = users.find(x => x.username === username && x.password === password); if (!user) return error('Username or password is incorrect'); return ok({ id: user.id, username: user.username, firstName: user.firstName, lastName: user.lastName, role: user.role, token: `fake-jwt-token.${user.id}` }); } function getUsers() { if (!isAdmin()) return unauthorized(); return ok(users); } function getUserById() { if (!isLoggedIn()) return unauthorized(); if (!isAdmin() && currentUser().id !== idFromUrl()) return unauthorized(); const user = users.find(x => x.id === idFromUrl()); return ok(user); } 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 = { provide: HTTP_INTERCEPTORS, useClass: FakeBackendInterceptor, multi: true };
This JWT Interceptor is used to intercept http requests from the angular application and to add a JWT auth token in the Authorization header.
jwt.interceptor.ts
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 '../_services'; @Injectable() export class JwtInterceptor implements HttpInterceptor { constructor(private authenticationService: AuthenticationService) { } intercept(request: HttpRequest , next: HttpHandler): Observable > { 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); } }
Followings are the files under the “_models” folder:
The role model file consists of an enum that states the roles supported by our application.
role.ts:
export enum Role { User = 'User', Admin = 'Admin' }
The user model file is a small class consisting of the properties of a user along with their role as well as jwt auth token.
user.ts
import { Role } from "./role"; export class User { id: number; username: string; password: string; firstName: string; lastName: string; role: Role; token?: string; }
The following files are put under the “_service” folder:
This service is used to login & logout in the Angular application. When the user logs in and out, it will be notified to other components.
authentication.service.ts
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 } from '../_models'; @Injectable({ providedIn: 'root' }) export class AuthenticationService { private currentUserSubject: BehaviorSubject ; public currentUser: Observable ; constructor(private http: HttpClient) { this.currentUserSubject = new BehaviorSubject (JSON.parse(localStorage.getItem('currentUser'))); this.currentUser = this.currentUserSubject.asObservable(); } public get currentUserValue(): User { return this.currentUserSubject.value; } login(username: string, password: string) { return this.http.post (`${environment.apiUrl}/users/authenticate`, { username, password }) .pipe(map(user => { if (user && user.token) { localStorage.setItem('currentUser', JSON.stringify(user)); this.currentUserSubject.next(user); } return user; })); } logout() { localStorage.removeItem('currentUser'); this.currentUserSubject.next(null); } }
The user service consists of methods to retrieve user data from the api. This acts as the interface between our Angular application and the backend api.
user.service.ts
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { environment } from '@environments/environment'; import { User } from '../_models'; @Injectable({ providedIn: 'root' }) export class UserService { constructor(private http: HttpClient) { } getAll() { return this.http.get (`${environment.apiUrl}/users`); } getById(id: number) { return this.http.get (`${environment.apiUrl}/users/${id}`); } }
Structure of the project:
Figure 1 Structure of the project
The “index.ts” files in every folder are barrel files that cluster the exported modules from a folder together so that they will be imported using the folder path instead of importing the full module path, as well as to enable importing of multiple modules in a single import.
When we run the project by executing the ng serve –-open command we will get the following output:
Figure 2 Login Page
When we try to log in as a normal user by entering Username=” user” and Password=” user”, we can access the only home page.
Figure 3 Home page as a User
A normal user can logout by entering on Logout tab.
When someone tries to login as an admin by entering Username=”admin” and password=”admin”, he/she be able to access the home page as well as the admin page.
Home page
Figure 4 Home page as an Admin
Admin Page
Figure 5 Admin Page
Role-Based-Authorization is necessary for any organization to protect the resources from being used by an unauthorized person as in any organization many employees use one system.
RBA allows employees of an organization to have access rights only to the information they require to do their tasks and restricts them from accessing information that doesn't concern with their role.
July 29, 2021
July 22, 2021
July 20, 2021
July 16, 2021
Well do everything we can to make our next best project!
Check out our most recent blogs
July 29, 2021
What is Angular? Angular is a frontend development framework used for building single-page client applications using HTML and Typescript. It is written in Typescript. What...
July 22, 2021
What is a Compiler? A compiler is nothing but a part of code that converts one programming language to another. If we talk about some simple programming languages like C, C++,...
July 20, 2021
Introduction Angular is a remarkable framework that may be used to create mobile and desktop web apps with spectacular UIs. It's made with JavaScript. we can utilize HTML, CSS,...
Well do everything we can to make our next best project!