In this article, we will discuss the implementation of JSON Web Token Authentication in Angular 8 application by using angular2-jwt
package.
This post is compatible with Angular v6+ and RxJS v6+
What is JWT?
JWT or JSON Web Token is an encrypted string which contains information related to the logged in user and secret key, this token is generated at server end after a user is authentication. This JWT is used for further communication between clients and servers.
After getting JWT from the server, every HTTP call to RESTful APIs must have an Authorization header set with JWT, which is encoded at the server end for authenticating the source. After successful validation data is transferred.
Implement JWT in Angular Application
In SPAs (Single Page Applications) like Angular JWT can be implemented very easy which will have a flow like this:
Step 1) User Logs in using Email/Password.
Step 2) After successfully authenticating the user, a JWT is generated and sent back to the client.
Step 3) Now client application needs to store the token received and send it in RESTful APIs as Authorization Headers, which will be decoded for verification at the server to process and return a response.
Also Check: Firebase/ PHP-jwt tutorial with Demo Server code
In Angular application, we can use angular2-jwt
package to easily manage JWT after getting it from the server-side. This package takes all responsibility to attach Token in API calls and manages to check the timeout of a token.
Letβs start implementation!
Create a new Angular 8 Project
Run the following command to create a new Angular project using Ng CLI:
$ ng new angular-jwt-demo
? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? CSS
Adding HttpClient
For making HTTP calls to the server for consuming RESTfull APIs we need to import HttpClientModule in our application.
Navigate to app.module.ts file then import HttpClientModule
from @angular/common/http
package. After that add in imports array as shown below:
// app.module.ts import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { FormsModule } from '@angular/forms'; import { HttpClientModule } from '@angular/common/http'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, AppRoutingModule, FormsModule, HttpClientModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
Above we also added FormsModule
as required by forms on our Login page.
Auth Service
Next, we will create a common Auth service to keep HTTP calls separate at one place. This service will have methods for Login, Logout and fetch Private data by making RESTful API calls to the server.
Run the following command to generate a new Auth service under _service
folder:
$ ng generate service _services/auth
Now open auth.service.ts file and import http
and router
classes:
// auth.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { retry, catchError } from 'rxjs/operators';
import { LoginResponse } from '../_models/user';
import { Router } from '@angular/router';
@Injectable({
providedIn: 'root'
})
export class AuthService {
// API path
basePath = 'https://my-site.com/server/';
constructor(
private router: Router,
private http: HttpClient
) { }
// Http Options
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
};
// Handle API errors
handleError(error: HttpErrorResponse) {
if (error.error instanceof ErrorEvent) {
// A client-side or network error occurred. Handle it accordingly.
console.error('An error occurred:', error.error.message);
} else {
// The backend returned an unsuccessful response code.
// The response body may contain clues as to what went wrong,
console.error(
`Backend returned code ${error.status}, ` +
`body was: ${error.error}`);
}
// return an observable with a user-facing error message
return throwError(
'Something bad happened; please try again later.');
}
// Verify user credentials on server to get token
loginForm(data): Observable<LoginResponse> {
return this.http
.post<LoginResponse>(this.basePath + 'api.php', data, this.httpOptions)
.pipe(
retry(2),
catchError(this.handleError)
);
}
// After login save token and other values(if any) in localStorage
setUser(resp: LoginResponse) {
localStorage.setItem('name', resp.name);
localStorage.setItem('access_token', resp.access_token);
this.router.navigate(['/dashboard']);
}
// Checking if token is set
isLoggedIn() {
return localStorage.getItem('access_token') != null;
}
// After clearing localStorage redirect to login screen
logout() {
localStorage.clear();
this.router.navigate(['/auth/login']);
}
// Get data from server for Dashboard
getData(data): Observable<LoginResponse> {
return this.http
.post<LoginResponse>(this.basePath + 'api.php', data, this.httpOptions)
.pipe(
retry(2),
catchError(this.handleError)
);
}
}
As you can see we also have an interface named user
.
Run the following command to create one under _models
folder:
$ ng generate interface _models/user
replace user.ts file with the following interface LoginResponse
:
// user.ts
export interface LoginResponse {
access_token: string;
data: any;
name: string;
status: string;
message: string;
}
In Auth service we have added some stuff, let's quickly discuss that:
basePath: Define API path.
httpOptions: Option object to set HTTP headers.
handleError: Method to handle client or server errors, I got this from Angular Docs.
loginForm: Method for making HTTP post call to API with Email and Password information. This method will be called from Login page.
setUser: After successfully authenticating the credential server will return some more user info with JSON Web Token, which we will save in browser's local storage to use later.
isLoggedIn: This method will only check if user's token is set in localStorage.
logout: This method simply clears the localStorage and navigates back to login screen.
Install angular2-jwt
In Angular application, we can easily manage JWT token by installing angular2-jwt package. It includes many features which otherwise are required and takes much effort for custom development.
- No need to use Angular Interceptors to add JWT in HTTP headers.
- Easy and one place configuration.
- Whitelist & Blacklist URLs
- Custom header and auth schema.
- Token expiration and other callbacks.
Run the following command to install angular2-jwt
using NPM command:
$ npm install @auth0/angular-jwt
Now open app.module.ts file import JwtModule
module then add in imports
array:
// app.module.ts ... ... import { JwtModule } from '@auth0/angular-jwt'; @NgModule({ declarations: [ AppComponent ], imports: [ .... .... JwtModule.forRoot({ config: { tokenGetter: () => { return localStorage.getItem('access_token'); }, whitelistedDomains: ['localhost'], blacklistedRoutes: ['localhost/auth/login'] } }) ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
Create Login and Dashboard Components
To replicate a real-world application scenario, create login
and dashboard
components.
The Login component will have login form which will return token from the server. After that user will be redirected to the Dashboard page and make a getData HTTP post-call from the RESTful API endpoint.
That request will be succeeded by the server after checking JWT Authorization
headers in the call which are getting handled by the angular2-jwt
package.
Now run the following commands to create Login and Dashboard components under _component
folder:
$ ng generate component _component/login
$ ng generate component _component/dashboard
Update Component's Template and Class
For UI styling we have added bootstrap.css file in index.html
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
Login Component
Replace below code in login.component.html and login.component.ts files:
<!-- login.component.html -->
<div class="container" style="display: flex;justify-content: center;align-items: center;height: 100vh;">
<div class="card" style="width: 50%;">
<div class="card-body">
<h5 class="card-title">Login Form</h5>
<div class="card-text">
<form (ngSubmit)="login()" #form="ngForm">
<div class="form-group">
<label for="exampleInputEmail1">Email address</label>
<input type="email" class="form-control" id="exampleInputEmail1" name="email" placeholder="Enter email" [(ngModel)]="model.email" required />
</div>
<div class="form-group">
<label for="exampleInputPassword1">Password</label>
<input type="password" class="form-control" id="exampleInputPassword1" name="password" placeholder="Password" [(ngModel)]="model.password" required />
</div>
<button type="submit" class="btn btn-primary" [disabled]="submitted || !form.valid">Submit</button>
</form>
</div>
</div>
</div>
</div>
In the Login component class, we are calling the Auth service method loginForm
on form submit:
// login.component.ts
import { Component, OnInit } from '@angular/core';
import { AuthService } from '../../_services/auth.service';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
model: any = {};
constructor(
private authService: AuthService
) {}
ngOnInit() {
this.authService.logout();
}
login() {
this.model.action = 'login';
this.authService.loginForm(this.model).subscribe(response => {
if (response.status === 'success') {
this.authService.setUser(response);
}
}, error => {
console.error(error);
});
}
}
Dashboard Component
After a successful login, the user is navigated to the Dashboard
page. In the Dashboard component, we are making an API call with JWT
token in Authorization
Headers to get data from the database only after verification.
Later we will also discuss Server PHP code on how we can generate a token on login and how to verify on subsequent API calls.
Now replace below code in Dashboard template and Component class:
<!-- dashboard.component.html -->
<div class="container" style="display: flex;justify-content: center;align-items: center;height: 100vh;">
<div class="card" style="width: 90%;">
<div class="card-body">
<h5 class="card-title">Dashboard Data <button type="button" class="float-right btn btn-outline-danger btn-sm" (click)="logout()">Logout</button></h5>
<div class="card-text">
<table class="table table-striped table-dark">
<thead>
<tr>
<th scope="col">Latitude</th>
<th scope="col">Longitude</th>
<th scope="col">Accuracy</th>
<th scope="col">Timestamp</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of dataFromServer">
<td>{{item.Latitude}}</td>
<td>{{item.Longitude}}</td>
<td>{{item.Accuracy}}</td>
<td>{{item.Timestamp}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
The dashboard will look like this after populating data from server:
// dashboard.component.ts
import { Component, OnInit } from '@angular/core';
import { AuthService } from '../../_services/auth.service';
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent implements OnInit {
model: any = {};
dataFromServer: any = [];
constructor(
private authService: AuthService
) { }
ngOnInit() {
this.getSomePrivateStuff();
}
getSomePrivateStuff() {
this.model.action = 'stuff';
this.authService.getData(this.model).subscribe(response => {
if (response.status === 'success') {
this.dataFromServer = response['data']['Coords'];
}
}, error => {
this.authService.logout();
});
}
logout(){
this.authService.logout();
}
}
Auth Gaurd
Angular Guards are used to preventing Unauthorised access to private pages like here we have Dashboard, these will only be visible to log in users.
Run following generate a command to create a new Auth guard implements CanActivate
:
$ ng generate guard _guards/auth
? Which interfaces would you like to implement?
>( ) CanActivate
Now replace the content of _guards/auth.guard.ts file with the following code:
// auth.guard.ts import { Injectable } from '@angular/core'; import { CanActivate, Router } from '@angular/router'; import { AuthService } from '../_services/auth.service'; @Injectable({ providedIn: 'root' }) export class AuthGuard implements CanActivate { constructor( private router: Router, private authService: AuthService ) { } canActivate() { if (!this.authService.isLoggedIn()) { this.router.navigate(['/auth/login']); return false; } else { return true; } } }
Update Routing
As now we have guards to prevent Dashboard page access by a user without login in the application, finally out app-routing.module.ts file will look like this:
// app-routing.module.ts import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { AuthGuard } from './_guards/auth.guard'; import { DashboardComponent } from './_component/dashboard/dashboard.component'; import { LoginComponent } from './_component/login/login.component'; const routes: Routes = [ { path: 'dashboard', canActivate: [AuthGuard], component: DashboardComponent }, { path: 'login', component: LoginComponent, }, { path: '', redirectTo: '/login', pathMatch: 'full' }, { path: '**', redirectTo: '/login' }, ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }
Now if you try to navigate "http://localhost:4200/dashboard" it will be redirected to the Login page if not logged in as there is AuthGuard
.
Finally, our project directory will look like this:
So up to here, we are done on client-side application implementation and usage of Token using angular2-jwt
package in Angular 6+ application.
Next, we will discuss, How to implement server-side code PHP to generate/ encode JWT and also verify/ decode it to access private data which we did above. For that, we will use firebase/php-jwt.
hi, Jolly. Greate tutorial, it help me. But…
Maybe you need to slightly change the part
blacklistedRoutes
becomesdisallowedRoutes
andwhitelistedDomains
becomesallowedDomains
. Because when we follow your tutorial which is pretty good, i install the angular-jwt version 5 automatically. In version 5, i found thatblacklistedRoutes
anddisallowedRoutes
have been replaced.https://github.com/auth0/angular2-jwt/releases/tag/v5.0.0
Thanks Jolly…
Good tutorial, my localstorage access token disappears when navigating to next page in angular 9 any help will be highly appreciated.