In this blog complete guilde is to help learn how to secure an Angular application by implementing user authentication and authorization.
Angular starter aplication to practice the following security concept:
Add User login and Logout
Role based route protect application
Get the starter Application
We have created starter project using then Angular CLI to help you learn Application Authentication and Authorization.
create new folder like: authenticate_angular. Install angular application globally in your and learn more about then click angular website
1. Create Angular application Using CLI.
ng new <project name> // authenticate_angular
cd authenticate_angular
2. First create new folder under src which name is auth where I have all component related to authentication. Create first component for login.
ng g c login
3. Create UI in login.html, so that we can send user credentials detail to API. we are using template-driven for create login
<!-- login.html -->
<section class="vh-100">
<div class="container py-5 h-100">
<div class="row d-flex justify-content-center align-items-center h-100">
<div class="col-12 col-md-8 col-lg-6 col-xl-5">
<div class="card shadow-2-strong" style="border-radius: 1rem;">
<form #loginForm="ngForm" (ngSubmit)="submit(loginForm)">
<div class="card-body p-5 text-center">
<h3 class="mb-5">Sign in</h3>
<div class="form-outline mb-4">
<label class="form-label" for="typeEmailX-2">Email</label>
<input type="email" name="email" ngModel #email="ngModel" required email class="form-control form-control-lg" />
@if(email.invalid && email.touched || isSubmit){
@if(email.hasError('required')){
<span class="text-danger">Email is required.</span>
}
@if(email.hasError('email')){
<span class="text-danger">Email is not valid.</span>
}
}
</div>
<div class="form-outline mb-4">
<label class="form-label" for="typePasswordX-2">Password</label>
<input type="password" name="password" ngModel #password="ngModel" required class="form-control form-control-lg" />
@if(password.invalid && password.touched || isSubmit){
@if(password.hasError('required')){
<span class="text-danger">Password is required.</span>
}
}
</div>
<!-- Checkbox -->
<div class="form-check d-flex justify-content-start mb-4">
<input class="form-check-input" type="checkbox" value="" id="form1Example3" />
<label class="form-check-label" for="form1Example3"> Remember password </label>
</div>
<button class="btn btn-primary btn-lg btn-block" type="submit">Login</button>
</div>
</form>
</div>
</div>
</div>
</div>
</section>
here html for login we are using template-drive form to bind data. new create submit process in login.ts file.
4. login.ts
-> isSubmit: we are showing on submit error, if all validation not matched.
-> authService: Inject authService for post data to API.
-> router:Inject to redirect route after login successfully.
-> route(ActivatedRoute): find return url if available.
// login.ts
import { Component, inject } from '@angular/core';
import { AuthService } from '../../../core/services';
import { NgForm } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrl: './login.component.scss'
})
export class LoginComponent {
isSubmit:boolean = false;
private authService = inject(AuthService);
private router = inject(Router);
private route = inject(ActivatedRoute);
private returnUrl:string;
constructor(){
this.returnUrl = this.route.snapshot.queryParamMap.get('returnUrl') || '/' // default route
}
submit(loginForm:NgForm){
if(loginForm.invalid) this.isSubmit = true;
if(loginForm.valid){
this.authService.login(loginForm.value)
.subscribe({
next:(res)=>{
if(res){
this.router.navigate([this.returnUrl]);
}
}
})
}
}
}
5. Create auth.service.ts.
AuthService is used for manage our login,logout and token management.
1. TOKEN_KEY: A key name ("my-token") for store user authentication token in localStorage.
2. USER_DETAILS: A key name("my-user") for store user details in localStorage.
3. token:Holds the currently authentication token.
4. isAuthenticatedSubject: An observable derived from isAuthenticatedSubject that store a single boolean value to indicating the user's authentication status.
5. isAuthenticated: An observable derived from isAuthenticatedSubject that other components can subscribe to for real-time updates on authentication status.
6. HttpClient: Injects HttpClient for HTTP requests.
7. Router: Router for navigating routes.
8. loadToken(): Initialize the authentication state based on any existing token in localStorage.
9. login(data:any): this method is used for send data to API URL . API response return token and user details. then save token and localStorage and update isAuthenticatedSubject to true, indecating the user is now logged in.
10. loadToken(): loadToken() check token exists in localStorage or not. If it exists , the token loaded into then token property, and isAuthenticatedSubject is set to true.If not, isAuthenticatedSubject is set to false, indicating the user is not logged in.
11. logout(): Removes the token and user details from localStorage. Sets isAuthenticatedSubject to false to update authentication status. and navigates the user to the login page.import { HttpClient } from '@angular/common/http';
//auth.service.ts
import { Injectable } from '@angular/core';
import { environment } from '../../../environments/environment';
import { map, ReplaySubject, tap } from 'rxjs';
import { Router } from '@angular/router';
export const TOKEN_KEY = "my-token";
export const USER_DETAILS = "my-user";
@Injectable({
providedIn: 'root'
})
export class AuthService {
public token = '';
public isAuthenticatedSubject = new ReplaySubject<boolean>(1);
public isAuthenticated = this.isAuthenticatedSubject.asObservable();
constructor(private http:HttpClient, private router:Router) {
this.loadToken();
}
login(data:any){
return this.http.post<any>(`${environment.BASE_URL}/auth/Login`,data)
.pipe(map((res)=>{
localStorage.setItem(TOKEN_KEY, res.token);
localStorage.setItem(USER_DETAILS, JSON.stringify(res.user));
return res;
}),tap((data)=>{
this.isAuthenticatedSubject.next(true);
}))
}
loadToken(){
const token = localStorage.getItem(TOKEN_KEY);
if(token){
this.token =token;
this.isAuthenticatedSubject.next(true);
}else{
this.isAuthenticatedSubject.next(false);
}
}
logout(){
localStorage.removeItem(TOKEN_KEY);
localStorage.removeItem(USER_DETAILS);
this.isAuthenticatedSubject.next(false);
this.router.navigateByUrl('/auth/login',{replaceUrl:true});
}
}
6. Create Guard for validate route.
1. create auth.guard.ts
authGuard checks if a user is authenticated before allowing access to particular route. If the user is not authenticated, they are redirected to the login page with a returnUrl.
- authGuard: authGuard is definded as a CanActivateFn function, which return an Observable<boolean>, Promise<boolean>, or boolean.
- authService.isAuthenticated: Which is representing then user's authentication status changes.
- handleAuthorization: it is receives the authentication status, the current route's snapshot (state), and router instance (router)
//auth-guard.ts
import { Router, type CanActivateFn, type RouterStateSnapshot } from '@angular/router';
import { Observable, count, tap } from 'rxjs';
import {inject } from '@angular/core';
import { AuthService } from '../services';
export const authGuard: CanActivateFn = (route, state):Observable<boolean>| Promise<boolean>|boolean => {
const accountService:AuthService = inject(AuthService);
const router:Router = inject(Router);
return accountService.isAuthenticated.pipe(tap(isAuthenticated => handleAuthorization(isAuthenticated,state,router)))
};
function handleAuthorization(isAuthenticated:boolean,state:RouterStateSnapshot, router:Router){
if(!isAuthenticated){
router.navigate(['/login'], {queryParams:{
returnUrl:state.url
}})
}
}
2. create auto-login.guard.ts
autoLoginGuard checks if a user is already authenticated and has a valid token. If the user is authencated, they are automatically redirected to the / (default route) route, preventing then from accessing routes like the login page again. if the user is not authenticated, the guard allows access to the requested route.
//auto-login.guard.ts
import { CanActivateFn, Router } from '@angular/router';
import { Observable, map } from 'rxjs';
import { inject } from '@angular/core';
import { AuthService, TOKEN_KEY } from '../services';
export const autoLoginGuard: CanActivateFn = (route, state): Observable<boolean> | Promise<boolean> | boolean => {
const accountService: AuthService = inject(AuthService);
const router: Router = inject(Router);
return accountService.isAuthenticated.pipe((map(isAuthenticated => {
if (isAuthenticated) {
const token = localStorage.getItem(TOKEN_KEY);
if (token) {
router.navigateByUrl('/loads', {replaceUrl:true});
}
return false;
} else {
return true;
}
})))
};
3. Implement guard's in route.
app-routing.module.ts, there are two routes defined in an Angular application.
1. Home Route: This is default route.
2. Login Route: This route is for the login page.
3. Guard: authGuard restricts access to this route, The authGuard will check is the user is authrized to access the HomeComponent. If the guard returns true, then user is allowed to access homeComponent, otherwise the user will be redirected to login page.
autoLoginGuard checks if the user is already logged in. is autoLoginGuard returns true, the user may be redirected to homeComponent.
//app-routing.module.ts
{
path:'',component:HomeComponent,
canActivate:[authGuard],
},
{
path:'/login',component:LoginComponent,
canActivate:[autoLoginGuard]
We are buiding a complete authentication and authorization in Angular.
Login to leave a comment.