Here we are implementing a login page for angular 8 or 9 using JWT token from rest API. Implementing auth interceptor for adding a token into the HTTP request for calling rest api with authorization token.
1) Node js
2) Angular CLI
3) Bootstrap
Follow the below steps
1) Create Angular App
ng new login_system
This will create login_system angular project.
2) Open this app project in visual code or any other which is used by you.
3) Install all prerequisites packages for angular app
npm install jquery bootstrap ngx-webstorage --save
In this project we need bootstrap, jquery if needed and ngx-webstorage.
ngx-webstorage this library provides an easy to use service to manage the web storages (local and session) from your Angular application. It provides also two decorators to synchronize the component attributes and the web storages. We can also use simple localstorage instead of ngx-webstorage.
4) Go to src > angular.json
file to register jquery and bootstrap in style and script see below code.
// angular.json
"styles": [
"src/styles.scss",
"node_modules/bootstrap/dist/css/bootstrap.css"
],
"scripts": [
"node_modules/jquery/dist/jquery.min.js",
]
5) Create components for login, home and profile page.
Here we have two ways to create components i.e. one is create single component and another is create multiple component at time using for loop in command prompt.
To create single component just use
ng generate component component-name
or
ng g c component-name
To create multiple component in one command use this
for %n in (component-name-1, component-name-2, ...) do ng g c %n
Here we use for do loop for multiple time recurring method.
(component-name-1, component-name-2 ) this is used to add your component names
%n is variable to pass into generate component for passing your component name.
For our app create components.
ng g c components/home
ng g c components/profile
ng g c components/login
ng g c components/navbar
or
for %n in (navbar,home,profile,login) do ng g c components/%n
6) Create Services for login and profile
ng g s shared/services/login
ng g s shared/services/profile
ng g s shared/services/my-shared
ng g s shared/services/auth-guard
or
for %n in (profile,login,my-shared,auth-guard) do ng g s shared/services/%n
7) Create token interceptor.
ng g interceptor shared/intercetor/token
This will create all components, services and classes and interceptor into src > app directory.
8) Register profile and login services and interceptor into providers inside app.module.ts
providers : [
ProfileService, LoginService, MySharedService, AuthGuard,
{
provide: HTTP_INTERCEPTORS,
useClass: TokenInterceptor,
multi: true
},
],
9) Register http module, reactive form module and ngx web storage module into imports inside app.module.ts
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule,
ReactiveFormsModule,
NgxWebstorageModule.forRoot(),
],
Finally your app.module.ts is like bellow
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HomeComponent } from './components/home/home.component';
import { ProfileComponent } from './components/profile/profile.component';
import { LoginComponent } from './components/login/login.component';
import { ProfileService } from './shared/services/profile.service';
import { LoginService } from './shared/services/login.service';
import { NavbarComponent } from './components/navbar/navbar.component';
import { MySharedService } from './shared/services/my-shared.service';
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
import { TokenInterceptor } from './shared/intercetor/token.interceptor';
import { ReactiveFormsModule } from '@angular/forms';
import { NgxWebstorageModule } from 'ngx-webstorage';
import { AuthGuard } from './shared/services/authgurd.service';
@NgModule({
declarations: [
AppComponent,
HomeComponent,
ProfileComponent,
LoginComponent,
NavbarComponent
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule,
ReactiveFormsModule,
NgxWebstorageModule.forRoot(),
],
providers: [
ProfileService, LoginService, MySharedService, AuthGuard,
{
provide: HTTP_INTERCEPTORS,
useClass: TokenInterceptor,
multi: true
},
],
bootstrap: [AppComponent]
})
export class AppModule {}
10) Design simple navbar
// src > app > components > navbar > navbar.component.html
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="#">Authentication System</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav ml-auto">
<li class="nav-item" [routerLinkActive]="['active']">
<a class="nav-link" routerLink="home">Home</a>
</li>
<li class="nav-item" [routerLinkActive]="['active']" *ngIf="sharedService.isAuth">
<a class="nav-link" routerLink="profile">Profile</a>
</li>
<li class="nav-item" [routerLinkActive]="['active']" *ngIf="!sharedService.isAuth">
<a class="nav-link" routerLink="login">Login</a>
</li>
<li class="nav-item" *ngIf="sharedService.isAuth">
<a class="nav-link" href="#" (click)="logout()">Logout</a>
</li>
</ul>
</div>
</nav>
// src > app > components > navbar > navbar.component.ts
import { Component } from '@angular/core';
import { MySharedService } from 'src/app/shared/services/my-shared.service';
import { LoginService } from 'src/app/shared/services/login.service';
@Component({
selector: 'app-navbar',
templateUrl: './navbar.component.html',
styleUrls: ['./navbar.component.css']
})
export class NavbarComponent {
constructor(public sharedService : MySharedService, private authService : LoginService) {}
logout(){
this.authService.signOut();
}
}
11) App component like this and use router outlet to get other home, profile and login pages and add navbar selector to display navbar to all the pages.
// src > app > app.component.html
<app-navbar></app-navbar>
<router-outlet></router-outlet>
12) Login Page Code
// src > app > components > login > login.component.html
<div class="jumbotron text-center">
<h2>Login Component</h2>
</div>
<div class="container text-center">
<div class="row justify-content-center">
<div class="col-lg-4">
<form [formGroup]="complexForm" (ngSubmit)="signIn()">
<input type="text" id="userName" class="form-control" placeholder="Username"
[(ngModel)]="model.username" formControlName="username"
[ngClass]="{ 'is-invalid': !f.username.valid && f.username.errors && f.username.touched }">
<p *ngIf="f.username.errors" class="invalid-feedback">
<span *ngIf="f.username.errors.required && f.username.touched">Username is
required.</span>
</p>
<input type="text" id="userPassword" class="form-control" placeholder="Password"
[(ngModel)]="model.password" formControlName="password"
[ngClass]="{ 'is-invalid': !f.password.valid && f.password.errors && f.password.touched }">
<p *ngIf="f.password.errors" class="invalid-feedback">
<span *ngIf="f.password.errors.required && f.password.touched">Password is
required.</span>
</p>
<br>
<div class="wrapper">
<span class="group-btn">
<button type="submit" class="btn btn-primary btn-block">login</button>
</span>
</div>
</form>
</div>
</div>
</div>
// src > app > components > login > login.component.ts
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { LoginService } from 'src/app/shared/services/login.service';
import { Router } from '@angular/router';
class Auth {
username : string = '';
password : string = '';
}?
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
complexForm: FormGroup;
model: Auth;
constructor(private formBuilder: FormBuilder,private _authService : LoginService,private _route : Router) {
this.model = new Auth();
this.complexForm = this.formBuilder.group({
username: ['', Validators.required],
password: ['', Validators.required],
});
}
get f() { return this.complexForm.controls; }
ngOnInit(): void {
this._authService.signOut();
}
signIn() {
if (this.complexForm.invalid) {
for (var i in this.complexForm.controls) {
this.complexForm.controls[i].markAsTouched();
}
return;
}
let postData = {
username: this.model.username,
password: this.model.password,
}
this._authService.login(postData).subscribe(res=>{
this._route.navigate(["/home"]);
})
}
}
// src > app > shared > services > login.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpErrorResponse} from '@angular/common/http';
import { MySharedService } from './my-shared.service';
import { environment } from 'src/environments/environment';
import { Observable, throwError } from 'rxjs';
import { LocalStorageService } from 'ngx-webstorage';
import { map, catchError } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class LoginService {
headers: HttpHeaders = new HttpHeaders();
headerOptions: any;
constructor(private http: HttpClient,private _sharedService: MySharedService,private _localStorageService: LocalStorageService) {
this.headers.set('Content-Type', 'application/x-www-form-urlencoded');
this.headerOptions = {
headers: this.headers,
observe: 'response',
responseType: 'json'
}
this.sharedData();
}
sharedData() {
let authData = this._localStorageService.retrieve('AuthrizationData');
if (authData) {
this._sharedService.fullname = authData.fullname;
this._sharedService.token = authData.token;
this._sharedService.isAuth = true;
this._sharedService.token_type = authData.token_type;
} else {
this._sharedService.fullname = '';
this._sharedService.token = '';
this._sharedService.token_type = '';
this._sharedService.isAuth = false;
}
}
login(obj: Object): Observable<any> {
const url = environment.baseURI + "login";
const postData = obj;
return this.http.post(url, postData, this.headerOptions).pipe(
map((res: any) => {
console.log(res);
const response = res.body;
this.signOut();
this._localStorageService.store('AuthrizationData', { token: response.token, expire: response.expiry, token_type: response.token_type, fullname: response.name })
this.sharedData();
return true;
}),
catchError(e => this._error(e))
);
}
_error(error: HttpErrorResponse): any {
alert(error.error.error);
return throwError(error);
}
signOut() {
this._localStorageService.clear('AuthrizationData');
this.sharedData();
}
}
13) Home Page Code
// src > app > components > home > home.component.html
<div class="jumbotron text-center">
<h2>Home Component</h2>
<br>
<h5> Hello {{userName}} </h5>
</div>
<div class="container">
<div class="text-center">
<h5> This home page is seen without any authentication no need for login. </h5>
<br>
<h5 *ngIf="!isLoggedIn">
You need to login your account to see profile page.
</h5>
<h5 *ngIf="isLoggedIn">
You are now logged in you can see your profile page.
</h5>
</div>
</div>
// src > app > components > home > home.component.ts
import { Component, OnInit } from '@angular/core';
import { MySharedService } from 'src/app/shared/services/my-shared.service';
import { LoginService } from 'src/app/shared/services/login.service';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
userName: string = 'User';
isLoggedIn: boolean = false;
constructor(private sharedService: MySharedService, private authService: LoginService) { }
ngOnInit(): void {
this.isLoggedIn = this.sharedService.isAuth;
if (this.isLoggedIn) {
this.userName = this.sharedService.fullname;
}
}
}
14) Profile Page Code
// src > app > components > profile > profile.component.html
<div class="jumbotron text-center">
<h2>Profile Component</h2>
</div>
<div class="container">
<div class="text-center">
<h5> This profile page is seen only if you have authentication. </h5>
<br>
<h5> Name : <span style="color: brown;">{{model.first_name}} {{model.last_name}}</span></h5>
<h5> Email : <span style="color: brown;">{{model.email}}</span></h5>
<h5> Username : <span style="color: brown;">{{model.username}}</span></h5>
</div>
</div>
// src > app > components > profile > profile.component.ts
import { Component, OnInit } from '@angular/core';
import { ProfileService } from 'src/app/shared/services/profile.service';
class User {
email: string = '';
first_name: string = '';
last_name: string = '';
pk: number = 0;
username: string = '';
}
@Component({
selector: 'app-profile',
templateUrl: './profile.component.html',
styleUrls: ['./profile.component.css']
})
export class ProfileComponent implements OnInit {
model: User = new User();
constructor(private _service: ProfileService) {}
ngOnInit(): void {
this.getUserData();
}
getUserData() {
this._service.getUserInfo().subscribe(response => {
this.model = response;
})
}
}
// src > app > shared > services > profile.service.ts
import { Injectable } from '@angular/core';
import { HttpHeaders, HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { environment } from 'src/environments/environment';
import { map, catchError } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class ProfileService {
headers: HttpHeaders = new HttpHeaders();
headerOptions: any;
constructor(private http: HttpClient) {
this.headers.set('Content-Type', 'application/x-www-form-urlencoded');
this.headerOptions = {
headers: this.headers,
observe: 'response',
responseType: 'json'
}
}
getUserInfo(): Observable<any> {
const url = environment.baseURI + "userinfo";
return this.http.get(url, this.headerOptions).pipe(
map((res: any) => {
const response = res.body;
return response;
}),
catchError(e => this._error(e))
);
}
_error(error: HttpErrorResponse): any {
alert(error.error.error);
return throwError(error);
}
}
15) Make service file for storing all the data like get and set informational data for shared into every pages.
// src > app > shared > my-shared.services.ts
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class MySharedService {
static token: string = '';
get token(): string { return MySharedService.token; }
set token(val: string) { MySharedService.token = val; }
static fullname: string = '';
get fullname(): string { return MySharedService.fullname; }
set fullname(val: string) { MySharedService.fullname = val; }
static token_type: string = '';
get token_type(): string { return MySharedService.token_type; }
set token_type(val: string) { MySharedService.token_type = val; }
static isAuth: boolean = false;
get isAuth(): boolean { return MySharedService.isAuth; }
set isAuth(val: boolean) { MySharedService.isAuth = val; }
}
16) Add Interceptor into app for passing token authorization into every http api call
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
import { Observable } from 'rxjs';
import { isUndefined, isNullOrUndefined } from 'util';
import { MySharedService } from '../services/my-shared.service';
@Injectable()
export class TokenInterceptor implements HttpInterceptor {
constructor(private _sharedService: MySharedService) { }
intercept(req: HttpRequest<any>, next: HttpHandler):
Observable<HttpEvent<any>> {
if (!isUndefined(this._sharedService.isAuth)) {
var isAuth = this._sharedService.isAuth as boolean;
if (isAuth == true) {
const authHeader = this._sharedService.token_type + ' ' + this._sharedService.token;
if (!isNullOrUndefined(authHeader)) {
const authReq = req.clone({ headers: req.headers.set('Authorization', authHeader) });
return next.handle(authReq);
}
}
else {
const authReq = req.clone({ headers: req.headers.set('Access-Control-Allow-Origin', '*') });
return next.handle(req);
}
}
}
}
17) Use route guard for authentication pages. If user open that page forcefully and you want to show page only after login then need to add route guard.
// src > app > shared > services > authguard.service.ts
import { Injectable } from '@angular/core';
import { Router, CanActivate } from '@angular/router';
import { MySharedService } from './my-shared.service';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(public sharedservice: MySharedService, public router: Router) {}
canActivate(): boolean {
if (!this.sharedservice.isAuth) {
this.router.navigate(['login']);
return false;
}
return true;
}
}
18) Finally set all app routing with auth guard
// src > app > app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './components/home/home.component';
import { ProfileComponent } from './components/profile/profile.component';
import { LoginComponent } from './components/login/login.component';
import { AuthGuard } from './shared/services/authgurd.service';
const routes: Routes = [
{ path: "", redirectTo: "home", pathMatch: "full" },
{ path: "home", component: HomeComponent },
{ path: "profile", component: ProfileComponent, canActivate: [AuthGuard] },
{ path: "login", component: LoginComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Finally done.