How to Implement JWT Authentication in Angular 2023
JSON Web Token (jwt) is an open standard that allows two parties to securely send data as JSON objects.
In this article, we will implement jwt authentication in angular from scratch.
1. Let's install @angular/cli package.
npm install -g @angular/cli
2. Create a new angular project.
ng new angular-jwt --routing
cd angular-jwt
3. Start the application
ng s --o
4. Install bootstrap and import bootstrap.css in style.css
npm i bootstrap
@import "~bootstrap/dist/css/bootstrap.css";
Note:- I had already implemented node backend with JWT checkout the links below.
(a). JWT with Mongoose
(b). JWT with MySQL
Both above implementations have exact same APIs. so you can use anyone of them.
5. Clone the mongoose implementation.
git clone https://github.com/ultimateakash/node-mongoose-jwt.git
Install dependencies
cd node-mongoose-jwt
npm install
npm start
Now our backend server is running(http://localhost:3000). so we can use the following APIs in our angular application.
base url: http://localhost:3000/api
API | Method | Endpoint |
Login User | POST | /login |
Register User | POST | /register |
Get Profile | GET | /user |
Logout User | GET | /logout |
6. Create a few components.
ng g component components/login
ng g component components/register
ng g component components/profile
ng g component components/nav-bar
ng g component components/not-found
7. Update app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { LoginComponent } from './components/login/login.component';
import { ProfileComponent } from './components/profile/profile.component';
import { RegisterComponent } from './components/register/register.component';
import { NotFoundComponent } from './components/not-found/not-found.component';
const routes: Routes = [
{
path: '',
component: LoginComponent
},
{
path: 'register',
component: RegisterComponent
},
{
path: 'profile',
component: ProfileComponent
},
{
path: '**',
component: NotFoundComponent,
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
8. Next, Import HttpClientModule in app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component';
import { LoginComponent } from './components/login/login.component';
import { RegisterComponent } from './components/register/register.component';
import { ProfileComponent } from './components/profile/profile.component';
import { NavBarComponent } from './components/nav-bar/nav-bar.component';
import { NotFoundComponent } from './components/not-found/not-found.component';
@NgModule({
declarations: [
AppComponent,
LoginComponent,
RegisterComponent,
ProfileComponent,
NavBarComponent,
NotFoundComponent
],
imports: [
BrowserModule,
HttpClientModule,
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
9. open environment.ts and add API base URL
environments/environment.ts
export const environment = {
baseUrl: 'http://localhost:3000/api',
production: false
};
10. Create auth service.
ng g service services/auth
11. Open auth.service.ts and add the following code.
services/auth.service.ts
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { map, tap } from 'rxjs';
import { environment } from 'src/environments/environment';
@Injectable({
providedIn: 'root'
})
export class AuthService {
baseUrl = environment.baseUrl;
constructor(private http: HttpClient) { }
login(data: any) {
return this.http.post(`${this.baseUrl}/login`, data)
.pipe(map(result => {
localStorage.setItem('authUser', JSON.stringify(result));
return result;
}));
}
register(data: any) {
return this.http.post(`${this.baseUrl}/register`, data);
}
profile() {
return this.http.get(`${this.baseUrl}/user`);
}
logout() {
return this.http.get(`${this.baseUrl}/logout`)
.pipe(tap(() => {
localStorage.removeItem('authUser')
}));
}
getAuthUser() {
return JSON.parse(localStorage.getItem('authUser') as string);
}
get isLoggedIn() {
if (localStorage.getItem('authUser')) {
return true;
}
return false;
}
}
12. Next, create auth and error interceptors.
ng g interceptor interceptors/auth
ng g interceptor interceptors/error
13. Open auth.interceptor.ts and add the following code.
interceptors/auth.interceptor.ts
import { Injectable } from '@angular/core';
import {
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor
} from '@angular/common/http';
import { Observable } from 'rxjs';
import { AuthService } from '../services/auth.service';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(
private authService: AuthService
) { }
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
const authUser = this.authService.getAuthUser();
if (authUser && authUser.access_token) {
request = request.clone({
setHeaders: {
Authorization: `Bearer ${authUser.access_token}`
}
});
}
return next.handle(request);
}
}
14. Open error.interceptor.ts and add the following code.
interceptors/error.interceptor.ts
import { Injectable } from '@angular/core';
import {
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor
} from '@angular/common/http';
import { catchError, Observable, throwError } from 'rxjs';
import { AuthService } from '../services/auth.service';
@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
constructor(
private authService: AuthService
) { }
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
return next.handle(request).pipe(catchError(error => {
if (error?.status === 401) {
this.authService.logout();
location.reload();
}
return throwError(() => error?.error?.message);
}))
}
}
15. Next, Import these interceptors in app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { AppComponent } from './app.component';
import { LoginComponent } from './components/login/login.component';
import { RegisterComponent } from './components/register/register.component';
import { ProfileComponent } from './components/profile/profile.component';
import { NavBarComponent } from './components/nav-bar/nav-bar.component';
import { NotFoundComponent } from './components/not-found/not-found.component';
import { AuthInterceptor } from './interceptors/auth.interceptor';
import { ErrorInterceptor } from './interceptors/error.interceptor';
@NgModule({
declarations: [
AppComponent,
LoginComponent,
RegisterComponent,
ProfileComponent,
NavBarComponent,
NotFoundComponent
],
imports: [
BrowserModule,
HttpClientModule,
AppRoutingModule
],
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true },
],
bootstrap: [AppComponent]
})
export class AppModule { }
16. Create auth guard.
ng g guard guards/auth
17. Open auth.guard.ts and add the following code.
guards/auth.guard.ts
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { AuthService } from '../services/auth.service';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(
private authService: AuthService,
private router: Router
) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const authUser = this.authService.getAuthUser();
if (authUser) {
return true;
}
this.router.navigate(['/login'], {
queryParams: { returnUrl: state.url }
});
return false;
}
}
18. add AuthGuard on the protected route.
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { LoginComponent } from './components/login/login.component';
import { ProfileComponent } from './components/profile/profile.component';
import { RegisterComponent } from './components/register/register.component';
import { NotFoundComponent } from './components/not-found/not-found.component';
import { AuthGuard } from './guards/auth.guard';
const routes: Routes = [
{
path: '',
component: LoginComponent
},
{
path: 'register',
component: RegisterComponent
},
{
path: 'profile',
component: ProfileComponent,
canActivate: [AuthGuard]
},
{
path: '**',
component: NotFoundComponent,
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
19. Import FormsModule and ReactiveFormsModule in app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { AppRoutingModule } from './app-routing.module';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { AppComponent } from './app.component';
import { LoginComponent } from './components/login/login.component';
import { RegisterComponent } from './components/register/register.component';
import { ProfileComponent } from './components/profile/profile.component';
import { NavBarComponent } from './components/nav-bar/nav-bar.component';
import { NotFoundComponent } from './components/not-found/not-found.component';
import { AuthInterceptor } from './interceptors/auth.interceptor';
import { ErrorInterceptor } from './interceptors/error.interceptor';
@NgModule({
declarations: [
AppComponent,
LoginComponent,
RegisterComponent,
ProfileComponent,
NavBarComponent,
NotFoundComponent
],
imports: [
BrowserModule,
FormsModule,
ReactiveFormsModule,
HttpClientModule,
AppRoutingModule
],
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true },
],
bootstrap: [AppComponent]
})
export class AppModule { }
20. Install ngx-toastr for notification.
npm i ngx-toastr
21. Import ToastrModule and BrowserAnimationsModule in app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { AppRoutingModule } from './app-routing.module';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { ToastrModule } from 'ngx-toastr';
import { AppComponent } from './app.component';
import { LoginComponent } from './components/login/login.component';
import { RegisterComponent } from './components/register/register.component';
import { ProfileComponent } from './components/profile/profile.component';
import { NavBarComponent } from './components/nav-bar/nav-bar.component';
import { NotFoundComponent } from './components/not-found/not-found.component';
import { AuthInterceptor } from './interceptors/auth.interceptor';
import { ErrorInterceptor } from './interceptors/error.interceptor';
@NgModule({
declarations: [
AppComponent,
LoginComponent,
RegisterComponent,
ProfileComponent,
NavBarComponent,
NotFoundComponent
],
imports: [
BrowserModule,
FormsModule,
ReactiveFormsModule,
HttpClientModule,
BrowserAnimationsModule,
ToastrModule.forRoot(),
AppRoutingModule
],
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true },
],
bootstrap: [AppComponent]
})
export class AppModule { }
22. Open style.css and import toastr css
@import "~bootstrap/dist/css/bootstrap.css";
@import '~ngx-toastr/toastr';
23. Open app.component.html and add the following code.
<div class="container">
<app-nav-bar></app-nav-bar>
<router-outlet></router-outlet>
</div>
24. Update nav-bar component
nav-bar/nav-bar.component.ts
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { ToastrService } from 'ngx-toastr';
import { AuthService } from 'src/app/services/auth.service';
@Component({
selector: 'app-nav-bar',
templateUrl: './nav-bar.component.html',
styleUrls: ['./nav-bar.component.css']
})
export class NavBarComponent implements OnInit {
constructor(
public authService: AuthService,
private toastr: ToastrService,
private router: Router
) { }
ngOnInit(): void { }
handleLogout() {
this.authService.logout().subscribe({
next: (result) => {
this.router.navigate(['/']);
},
error: (error) => {
this.toastr.error(error);
}
});
}
}
nav-bar/nav-bar.component.html
<header class="d-flex flex-wrap justify-content-center py-3 mb-4 border-bottom">
<div class="d-flex align-items-center mb-3 mb-md-0 me-md-auto text-dark text-decoration-none">
<span class="fs-4">Angular JWT</span>
</div>
<ul class="nav nav-pills">
<ng-container *ngIf="authService.isLoggedIn; else loginBlock">
<li class="nav-item"><a href="javascript:;" (click)="handleLogout()" class="nav-link active">Logout</a></li>
</ng-container>
<ng-template #loginBlock>
<li class="nav-item"><a [routerLink]="['/']" [routerLinkActive]="'active'" [routerLinkActiveOptions]="{exact: true}" class="nav-link">Login</a></li>
<li class="nav-item"><a [routerLink]="['/register']" [routerLinkActive]="'active'" class="nav-link">Register</a></li>
</ng-template>
</ul>
</header>
25. Update login component
login/login.component.ts
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { AuthService } from 'src/app/services/auth.service';
import { ToastrService } from 'ngx-toastr';
import { ActivatedRoute, Router } from '@angular/router';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
loginForm!: FormGroup;
returnUrl!: string;
isSubmitted: boolean = false;
constructor(
private formBuilder: FormBuilder,
private authService: AuthService,
private toastr: ToastrService,
private route: ActivatedRoute,
private router: Router
) { }
ngOnInit(): void {
this.loginForm = this.formBuilder.group({
email: ['', Validators.required],
password: ['', Validators.required]
});
this.returnUrl = this.route.snapshot.queryParams['returnUrl'];
}
onSubmit() {
this.isSubmitted = true;
this.authService.login(this.loginForm.value).subscribe({
next: (result) => {
this.router.navigate([this.returnUrl || '/profile']);
},
error: (error) => {
this.toastr.error(error);
}
})
.add(() => {
this.isSubmitted = false;
});
}
get loginFormControls() {
return this.loginForm.controls;
}
}
login/login.component.html
<div class="row">
<div class="col-6 offset-3">
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
<div class="mb-3">
<label for="inputEmail" class="form-label">Email address</label>
<input type="email" class="form-control" id="inputEmail" formControlName="email">
<div class="form-text text-danger" *ngIf="loginFormControls['email'].invalid && (loginFormControls['email'].dirty || loginFormControls['email'].touched)">
<ng-container *ngIf="loginFormControls['email'].errors?.['required']">
Please enter your email.
</ng-container>
</div>
</div>
<div class="mb-3">
<label for="inputPassword" class="form-label">Password</label>
<input type="password" class="form-control" id="inputPassword" formControlName="password">
<div class="form-text text-danger" *ngIf="loginFormControls['password'].invalid && (loginFormControls['password'].dirty || loginFormControls['password'].touched)">
<ng-container *ngIf="loginFormControls['password'].errors?.['required']">
Please enter your password.
</ng-container>
</div>
</div>
<button type="submit" class="btn btn-primary" [disabled]="!loginForm.valid || isSubmitted">Submit</button>
</form>
</div>
</div>
26. Update register component
register/register.component.ts
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { ToastrService } from 'ngx-toastr';
import { AuthService } from 'src/app/services/auth.service';
@Component({
selector: 'app-register',
templateUrl: './register.component.html',
styleUrls: ['./register.component.css']
})
export class RegisterComponent implements OnInit {
registerForm!: FormGroup;
isSubmitted: boolean = false;
constructor(
private formBuilder: FormBuilder,
private authService: AuthService,
private toastr: ToastrService,
private router: Router
) { }
ngOnInit(): void {
this.registerForm = this.formBuilder.group({
name: ['', Validators.required],
email: ['', Validators.required],
password: ['', [Validators.required, Validators.pattern(/(?=.*\d)(?=.*[a-z])(?=.*[A-Z])/)]]
});
}
onSubmit() {
this.isSubmitted = true;
this.authService.register(this.registerForm.value).subscribe({
next: (result) => {
this.router.navigate(['/']).then(() => {
this.toastr.success('Registration successful. Please login.');
});
},
error: (error) => {
this.toastr.error(error);
}
})
.add(() => {
this.isSubmitted = false;
});
}
get registerFormControls() {
return this.registerForm.controls;
}
}
register/register.component.html
<div class="row">
<div class="col-6 offset-3">
<form [formGroup]="registerForm" (ngSubmit)="onSubmit()">
<div class="mb-3">
<label for="inputName" class="form-label">Name</label>
<input type="email" class="form-control" id="inputName" formControlName="name">
<div class="form-text text-danger" *ngIf="registerFormControls['name'].invalid && (registerFormControls['name'].dirty || registerFormControls['name'].touched)">
<ng-container *ngIf="registerFormControls['name'].errors?.['required']">
Please enter your name.
</ng-container>
</div>
</div>
<div class="mb-3">
<label for="inputEmail" class="form-label">Email address</label>
<input type="email" class="form-control" id="inputEmail" formControlName="email">
<div class="form-text text-danger" *ngIf="registerFormControls['email'].invalid && (registerFormControls['email'].dirty || registerFormControls['email'].touched)">
<ng-container *ngIf="registerFormControls['email'].errors?.['required']">
Please enter your email.
</ng-container>
</div>
</div>
<div class="mb-3">
<label for="inputPassword" class="form-label">Password</label>
<input type="password" class="form-control" id="inputPassword" formControlName="password">
<div class="form-text text-danger" *ngIf="registerFormControls['password'].invalid && (registerFormControls['password'].dirty || registerFormControls['password'].touched)">
<ng-container *ngIf="registerFormControls['password'].errors?.['required']">
Please enter your password.
</ng-container>
<ng-container *ngIf="registerFormControls['password'].errors?.['pattern'] && !registerFormControls['password'].errors?.['required']">
Password should contains a lowercase, a uppercase character and a digit.
</ng-container>
</div>
</div>
<button type="submit" class="btn btn-primary" [disabled]="!registerForm.valid || isSubmitted">Submit</button>
</form>
</div>
</div>
27. Update profile component
profile/profile.component.ts
import { Component, OnInit } from '@angular/core';
import { ToastrService } from 'ngx-toastr';
import { AuthService } from 'src/app/services/auth.service';
@Component({
selector: 'app-profile',
templateUrl: './profile.component.html',
styleUrls: ['./profile.component.css']
})
export class ProfileComponent implements OnInit {
user!: any;
constructor(
private authService: AuthService,
private toastr: ToastrService
) { }
ngOnInit(): void {
this.fetchProfile();
}
fetchProfile() {
this.authService.profile().subscribe({
next: (result) => {
this.user = result;
},
error: (error) => {
this.toastr.error(error);
}
});
}
}
profile/profile.component.html
<div class="row">
<div class="col-6 offset-3">
<ul class="list-group">
<li class="list-group-item"><span class="fw-bold">Id</span> - {{user?._id}}</li>
<li class="list-group-item"><span class="fw-bold">Name</span> - {{user?.name}}</li>
<li class="list-group-item"><span class="fw-bold">Email</span> - {{user?.email}}</li>
</ul>
</div>
</div>
28. Finally test the application.
Checkout my full angular-jwt example.
Leave Your Comment