Click Below to subscribe

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.

https://github.com/ultimateakash/angular-jwt 

Leave Your Comment