In the previous article, we described the process of developing Angular cookie-based authentication with role-based access. This article presents the final touches: first, adding a feature that displays a list of users for the Admin role and restricts access for the User role, and second, completing the error pages that were generated earlier.
Users Service
Generate users-service library:
npx nx g @nx/angular:library users-service \\
--prefix mtfs \\
--importPath=@mtfs/frontend/users-service \\
--tags type:feature,scope:frontend \\
--directory=libs/frontend/users/users-service \\
--projectNameAndRootFormat=as-provided
Generate UsersService class:
npx nx generate @schematics/angular:service --name=users \\
--project=users \\
--path=libs/frontend/users/users-service/src/lib/ \\
--no-interactive
Create getAll method in UsersService class:
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { IUser } from '@mtfs/shared/domain';
import { Observable } from 'rxjs';
const usersApi = `/users`;
@Injectable({
providedIn: 'root'
})
export class UsersService {
constructor(private http: HttpClient) { }
getAll(): Observable<IUser[]>{
return this.http.get<IUser[]>(usersApi);
}
}
Users component
The Users component will display the list of users and will be part of the users-ui library.
Generate users-ui library:
npx nx g @nx/angular:library users-ui\\
--prefix mtfs \\
--importPath=@mtfs/frontend/users-ui \\
--tags type:ui,scope:frontend \\
--directory=libs/frontend/users/users-ui \\
--projectNameAndRootFormat=as-provided
Update name of defolt UsersUiComponent to UsersComponent.
Update UsersComponent:
import { Component, OnInit, ViewChild } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatTableModule, MatTable, MatTableDataSource} from '@angular/material/table';
import { MatSort } from '@angular/material/sort';
import { MatCardModule } from '@angular/material/card'
import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
import { Router } from '@angular/router';
import { IUser } from '@mtfs/shared/domain';
import { UsersService } from '@mtfs/frontend/users-service';
@Component({
selector: 'mtfs-users',
standalone: true,
imports: [
CommonModule,
MatTableModule,
MatCardModule,
MatPaginatorModule],
templateUrl: './users.component.html',
styleUrl: './users.component.scss',
})
export class UsersComponent implements OnInit {
users!:IUser[];
displayedColumn: string[]= ['firstname', 'lastname', 'email', 'phoneNumber', 'role', 'createdAt'];
dataSource = new MatTableDataSource<IUser>(this.users);
@ViewChild(MatPaginator, {static: true}) paginator!: MatPaginator;
@ViewChild(MatSort, {static: true}) sort!: MatSort;
@ViewChild(MatTable, {static: false}) table!: MatTable<IUser>;
constructor(
private usersService: UsersService,
private router: Router
){}
ngOnInit(){
this.fetchUsers();
}
fetchUsers(){
this.usersService.getAll()
.subscribe((data: IUser[])=> {
this.users= data;
this.dataSource = new MatTableDataSource(this.users);
this.dataSource.paginator= this.paginator;
this.dataSource.sort = this.sort;
})
}
}
Update UsersComponent template to display users data:
<div class="mat-card-container table-card-container">
<mat-card class="mat-elevation-z0" >
<mat-card-title>
<h1 class="entity-title">Users</h1>
</mat-card-title>
<div class="table-container">
<table mat-table [dataSource]= "dataSource" matSort #table>
<!-- firstname -->
<ng-container matColumnDef="firstname">
<th mat-header-cell mat-sort-header *matHeaderCellDef>Firstname</th>
<td mat-cell *matCellDef= "let element" class='m-card-title' data-label="Firstname">
{{element.firstname}} {{element.lastname}}
</td>
</ng-container>
<!-- lastname -->
<ng-container matColumnDef="lastname">
<th mat-header-cell mat-sort-header *matHeaderCellDef>Lastname</th>
<td mat-cell *matCellDef= "let element" class='m-card-title' data-label="Lastname">
{{element.lastname}}
</td>
</ng-container>
<!-- email -->
<ng-container matColumnDef="email">
<th mat-header-cell mat-sort-header *matHeaderCellDef>Email</th>
<td mat-cell *matCellDef= "let element" class='m-card-sub-title' data-label="Email">
{{element.email}}
</td>
</ng-container>
<!-- phoneNumber -->
<ng-container matColumnDef="phoneNumber">
<th mat-header-cell mat-sort-header *matHeaderCellDef>Phone Number</th>
<td mat-cell *matCellDef= "let element" class='m-card-sub-title' data-label="Phone Number">
{{element.phoneNumber}}
</td>
</ng-container>
<!-- role -->
<ng-container matColumnDef="role">
<th mat-header-cell mat-sort-header *matHeaderCellDef>Role</th>
<td mat-cell *matCellDef= "let element" class='m-card-sub-title' data-label="Role">
{{element.role.name}}
</td>
</ng-container>
<!-- createdAt -->
<ng-container matColumnDef="createdAt">
<th mat-header-cell mat-sort-header *matHeaderCellDef>Created At</th>
<td mat-cell *matCellDef= "let element" class='has_label_on_mobile' data-label='Created At'>
{{element.createdAt}}
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef= "displayedColumn sticky:true" ></tr>
<tr mat-row *matRowDef= "let row; columns: displayedColumn"></tr>
</table>
<mat-paginator #paginator
[pageSizeOptions] = "[10, 50, 100]"
showFirstLastButtons>
</mat-paginator>
</div>
</mat-card>
</div>
Update routes in libs/frontend/users/users-ui/src/lib/lib.routes.ts:
import { Route } from '@angular/router';
import { UsersComponent } from './users/users.component';
import { authGuard } from '@mtfs/frontend/auth-guards';
import { RoleEnum } from '@mtfs/shared/enums';
export const usersUiRoutes: Route[] = [
{
path: '', component: UsersComponent,
canActivate: [authGuard],
data: {userRole: [RoleEnum.Admin]}
},
];
Update apps/frontend/src/app/app.routes.ts by adding the path ‘users’
//----------path for admin role------------//
{
path: '',
async loadComponent() {
const c = await import('@mtfs/frontend/ui-components');
return c.HomeComponent
},
canActivate: [authGuard],
children: [
{
path: 'users',
async loadComponent() {
const c = await import('@mtfs/frontend/users-ui');
return c.UsersComponent;
},
canActivate: [authGuard],
data: {userRole: RoleEnum.Admin}
},
]
},
Error pages
The last step is to customize the messages on the error page.
Not-found page
Update NotFoundComponent (libs/frontend/ui/error-pages/src/lib/not-found/not-found.component.ts):
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatCardModule } from '@angular/material/card';
import { MatButtonModule } from '@angular/material/button';
import { Router, RouterModule } from '@angular/router';
import { AuthService } from '@mtfs/frontend/auth-service';
@Component({
selector: 'mtfs-not-found',
standalone: true,
imports: [
CommonModule,
MatCardModule,
MatButtonModule,
RouterModule
],
templateUrl: './not-found.component.html',
styleUrl: './not-found.component.scss',
})
export class NotFoundComponent {
constructor(
private router: Router,
private authService: AuthService
){ }
click(){
if(this.authService.userValue){
this.router.navigate(["/"]);
}else {
this.router.navigate(["./login"]);
}
}
}
Update NotFoundComponent template (libs/frontend/ui/error-pages/src/lib/not-found/not-found.component.html):
<div class = "card-container">
<mat-card class="card">
<mat-card-header>
<mat-card-title >Page not found</mat-card-title>
</mat-card-header>
<mat-card-content>
</mat-card-content>
<mat-card-actions >
<button routerLink="/" mat-stroked-button class="basic-button" color="primary">Ок</button>
</mat-card-actions>
</mat-card>
</div>
Forbidden page
update ForbiddenComponent (libs/frontend/ui/error-pages/src/lib/forbidden/forbidden.component.ts):
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatCardModule } from '@angular/material/card';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
@Component({
selector: 'mtfs-forbidden',
standalone: true,
imports: [
CommonModule,
MatCardModule,
MatButtonModule,
RouterModule],
templateUrl: './forbidden.component.html',
styleUrl: './forbidden.component.scss',
})
export class ForbiddenComponent {}
update ForbiddenComponent template (libs/frontend/ui/error-pages/src/lib/forbidden/forbidden.component.ts):
<div class = "card-container">
<mat-card class="card">
<mat-card-header>
<mat-card-title>Access Denied</mat-card-title>
</mat-card-header>
<mat-card-content>
<p>You don't have permission to access this resource.</p>
</mat-card-content>
<mat-card-actions>
<button routerLink="/" class="basic-button" mat-stroked-button color="primary">Ок</button>
</mat-card-actions>
</mat-card>
</div>
Server Error page
update ServerErrorComponent (libs/frontend/ui/error-pages/src/lib/server-error/server-error.component.ts):
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatCardModule } from '@angular/material/card';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
@Component({
selector: 'mtfs-server-error',
standalone: true,
imports: [
CommonModule,
MatCardModule,
MatButtonModule,
RouterModule],
templateUrl: './server-error.component.html',
styleUrl: './server-error.component.scss',
})
export class ServerErrorComponent {}
Update ServerErrorComponent template (libs/frontend/ui/error-pages/src/lib/server-error/server-error.component.html)
<div class = "card-container">
<mat-card class="card">
<mat-card-header>
<mat-card-title >Server Error!</mat-card-title>
</mat-card-header>
<mat-card-content>
<p>Something went wrong! Please try again later</p>
</mat-card-content>
<mat-card-actions>
<button routerLink="/" class="basic-button" mat-stroked-button color="primary">Ок</button>
</mat-card-actions>
</mat-card>
</div>
How it work together
So now we can ready to check how to work authentication, authorization and role based access control
Run backend and fronted app parallel:
nx run-many -t serve -p backend frontend --parallel
And on http://localhost:4200/, we can see how the authentication features work::
The new user registration and logout features:
Login registered user, trying to go to the forbidden page (the User role can’t access the users page) and trying to goy to the not-existent page features:
Logging in as a user with the Admin role and displaying the Admin’s navigation menu with access to the Users page
Summary and Conclusions
In this article, we finalized the development of an Angular cookie-based authentication mechanism with role-based access control. The primary focus was on adding functionality for displaying a list of users for Admin role and restricting access for User role. We also completed the customization of various error pages, such as Not Found, Forbidden, and Server Error pages.
By the end of this article, we have successfully implemented and verified the following features:
- Role-Based Access Control: User roles determine access permissions, ensuring that only Admin role can access certain features such as list of users and each role has the own unique navigation panel.
- Error Pages: Custom error pages provide a better user experience when encountering Not Found, Forbidden, or Server Errors.
- Authentication Flow: The authentication process is seamless, supporting user registration, login, and logout, with appropriate redirection and access controls based on user roles.
These enhancements ensure a robust and user-friendly authentication system, ready for further development.
Discover more from More Than Fullstack
Subscribe to get the latest posts sent to your email.