In Part 10 – A, we developed the login API in node.js, in this part, We will develop the client-side in Angular. We will develop the Login/Logout functionality and learn how to use Angular JWT and browser local storage. The only authenticated user would be able to access the admin section after this part.
Introduction
In Part 10 – A, we developed the login API in node.js, used bcrypt
to compare the hashed password and jsonwebtoken
to create the public key that is embeded in successful login API response as a token
. In this part, we will develop the LoginComponent
and login & logout functionality.
Let’s Start
It is strongly recommended to read all previous parts before moving to this one, I can’t promise you would understand all the steps I am going to list down without knowledge of previous parts.
- Make sure Part 10 – A is working fine without any error.
- First, let’s create the
login
interface, in the mazharncoweb folder, right-click on src -> app -> model and select an option, New File, enter the file name login.ts. Add the following code in it:
- This is a very simple interface with two fields
UserName
andPassword
that will act as a model class for us. - Since we are using JSON Web Token (JWT) to avoid CSRF, we also need to angular-jwt package for some useful methods e.g. to check if a token is still valid or expired. Right click on a mazharncoweb folder and select the option, Open in Terminal, in TERMINAL tab, enter the command:
npm install @auth0/angular-jwt@^1.1.0 --save
, I am using the specific version because the latest version has some conflicts and errors. - Once the angular-jwt is successfully installed, we need to create the
AuthService
, though we will only make one POST call in this service I just want to keep it sperate fromDataService
because we also need to add some specific methods related to authentication. In the same TERMINAL tab, enter the command:ng g s service/auth/auth --m app.module.ts
- Go to src -> app -> service -> auth folder, edit the auth.service.ts file and add the following content in it:
- AuthService has five functions, let’s briefly understand each:
- authenticateUser(): This is the main Login API function that takes the Login API URL and user object (
UserName
andPassword
), calls the API and get the response back. This response includes the successful text message andtoken
as discussed in the last part. - storeUserData(): This method stores the token and expiration time in
localStorage
. ThelocalStorage
is an HTML 5 feature that stores the values as a key-value pair in the browser, e.g.access_token
is the key that has thetoken
value returns from Login API. The built-inlocalStorage.setItem()
method is used to set the value to a key. - getToken(): This method returns the
token
by key. The built-inlocalStorage.getItem()
method is used to get the key’s value. - isLoggedIn(): We are using the angular-jwt package we installed earlier to check if the
token
is still valid by checking its expiry time. - logout(): This method will clear everything from
localStorage
and redirect to the home page, since for each admin page, the guard will callisLoggedIn()
method, after clearinglocalStorage
, this method will returnfalse
and would not let the user enter in the admin section. You would see it in action when we will develop the guard.
- authenticateUser(): This is the main Login API function that takes the Login API URL and user object (
- Next, let’s create the
LoginComponent
, you can see the Login button at a top right corner from the beginning of series. Finally, this would get activated and open the login window in a modal popup. If you are not already in TERMINAL tab pointing to a mazharncoweb folder, right click on the mazharncoweb folder and select option, Open in Terminal, enter the command:ng g c client/login
- Edit the src -> app -> client -> login -> login.component.css and add the following CSS in it:
.header{
background: #3B5C89;
border-bottom: 5px solid #28527F;
height: 50px;
padding-left: 5px;
}
.header_title{
vertical-align:baseline;
padding-top: 10px;
padding-bottom: 0px;
padding-left: 5px;
font-size: 16pt;
font-family: Arial, Helvetica, sans-serif;
color: rgba(255, 255, 255, 0.85);
}
mat-card{
margin: 0px;
padding: 0px;
}
mat-card-content{
margin: 0px;
padding: 0px;
width: 400px;
height: 200px;
}
.mat-dialog-container {
padding: 0 !important;
margin: 0 !important;
}
.frm-ctrl {
width: 100%;
}
.card_cntnt
{
padding: 20px;
}
- Edit the src -> app -> client -> login -> login.component.html and add the following code in it:
<mat-card>
<mat-card-header class="header">
<mat-card-title class="header_title"><i class="material-icons"></i> Admin Login</mat-card-title>
</mat-card-header>
<mat-card-content class="card_cntnt" >
<div>
<form novalidate (ngSubmit)="onSubmitLgn(lgnFrm)" [formGroup]="lgnFrm">
<div>
<mat-form-field class="frm-ctrl">
<input matInput placeholder="Email Address" formControlName="EmailAddress">
<mat-error *ngIf="lgnFrm.controls['EmailAddress'].errors?.required">
User Name is required!
</mat-error>
</mat-form-field>
</div>
<div>
<mat-form-field class="frm-ctrl">
<input matInput type="password" placeholder="Password" formControlName="Password">
<mat-error *ngIf="lgnFrm.controls['Password'].errors?.required">
Password is required!
</mat-error>
</mat-form-field>
</div>
<div>
<button color="primary" type="submit" [disabled]="lgnFrm.invalid" mat-raised-button>Login</button>
<button color="warn" type="button" mat-raised-button (click)="dialogRef.close()">Cancel</button>
</div>
</form>
</div>
</mat-card-content>
</mat-card>
- This is pretty straightforward HTML for
LoginComponent
, we have Model-Driven formlgnFrm
that only has two controls,EmailAddress
andPassword
. We also specified the nice Angular Materialmat-error
control for required filed validation. TheonSubmitLgn()
is a function that will trigger when we will hit the Login button to submit the form. Since this component would open in a modal pop-up, the cancel button will hide the modal pop-up control by callingdialogRef.close()
method. - Edit the src -> app -> client -> login -> login.component.ts and add the following code in it:
- Let’s understand the code:
- On the top, we have the Login API URL and
FormGroup
object for Model-Driven form. - constructor(): In the
constructor
,- We have
formBuilder
service parameter for creating Model-Driven form, AuthService
that we created earlier to useauthenticateUser()
and other methods,Util
to open the snackbar component at the bottom for success or failure login API response,MatDialogRef
to open theLoginComponent
in a modal pop-up andRouter
to redirect to the Admin section.
- We have
- ngOnInit(): We are defining the
lgnFrm
with two controlsEmailAddress
andPassword
along with validations. Thanks to Angular Model-Driven form built-in validation for length and email address. - onSubmitLgn(): This is login form submit function that takes the user’s entered an email address and password, calls the
authenticateUser()
function fromAuthService
and check the response. If user credentials are authenticated, we are calling astoreUserData()
method fromAuthService
to store thetoken
,expireIn
(token expiration time) and user information. After that, the user is redirected to the admin home page.
- On the top, we have the Login API URL and
- The
LoginComponent
will be open in modal pop-up control so we will explicitly add it inAppModule
‘sentryComponents
, edit the src -> app -> app.module.ts and replace its content with the following:
- Now we have developed the LoginComponent, its time to enable the login button on the top right corner, this button is defined in
AppComponent
so edit the src -> app -> app.component.html and replace its content with following:
<div class="main_div">
<mat-grid-list cols="10" rowHeight="2:1">
<div>
<mat-grid-tile>
<div style="padding-left: 5px;">
<a [routerLink]="['home']"> <img src='assets/images/logo1.png' class="mnc-logo" /></a>
</div>
</mat-grid-tile>
<mat-grid-tile [colspan]=8>
<div class="socail_btn_padding">
<a target="_blank" href="https://www.facebook.com/mazhar.mahmood.16"><img src="assets/images/fb_btn1.jpg" class="socail_btn image" /></a>
<a href="skype:plug-shop?mazharmahmood786"><img src="assets/images/skype_btn1.jpg" class="socail_btn image" /></a>
<img src="assets/images/twtr_btn1.jpg" class="socail_btn image" />
<img src="assets/images/lkdin_btn1.jpg" class="socail_btn image" />
</div>
<div style="padding-left: 100px" *ngIf="_authService.isloggedIn();">
<img src="assets/images/admin.png" style="width:150px; height: 48px" class="lgn_btn image" (click)="redirectToAdmin()" />
</div>
</mat-grid-tile>
<mat-grid-tile [colspan]=1>
<div style="text-align: right" *ngIf="!_authService.isloggedIn(); else logout">
<img src="assets/images/login_btn.png" class="lgn_btn image" (click)="openDialog()" />
</div>
<ng-template #logout>
<div>
<img src="assets/images/logout_btn.png" class="lgn_btn image" (click)="_authService.logout()" />
</div>
</ng-template>
</mat-grid-tile>
</div>
<mat-grid-tile [colspan]=10 [rowspan]=3>
<ngx-carousel [inputs]="carouselBanner" [moveToSlide]="1">
<ngx-item NgxCarouselItem>
<div><img src='assets/images/banner_1.jpg' width="100%" /></div>
</ngx-item>
<ngx-item NgxCarouselItem>
<div><img src='assets/images/banner_2.jpg' width="100%" /></div>
</ngx-item>
<button NgxCarouselPrev><</button>
<button NgxCarouselNext>></button>
</ngx-carousel>
</mat-grid-tile>
</mat-grid-list>
<div>
<nav class="navbar navbar-default navbar_shadow">
<div class="container-fluid">
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a [routerLink]="['home']">Home</a></li>
<li class="dropdown" *ngFor="let menu of mainMenus" style="cursor: pointer;">
<a [routerLink]="['page', menu.MenuCode]" class="dropdown-toggle" role="button" aria-haspopup="true" aria-expanded="false">{{menu.MenuName}}<span *ngIf="getChildMenu(menu.MenuCode).length>0" class="caret"></span></a>
<ul class="dropdown-menu" *ngIf="getChildMenu(menu.MenuCode).length>0">
<li *ngFor="let chldMenu of getChildMenu(menu.MenuCode)">
<a [routerLink]="['page', chldMenu.MenuCode]">{{chldMenu.MenuName}}</a>
</li>
</ul>
</li>
<li><a [routerLink]="['contact']">Contact Us</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<form class="navbar-form navbar-left" novalidate (ngSubmit)="onSubmit(searchFrm)" [formGroup]="searchFrm">
<div class="form-group">
<input type="text" class="form-control" formControlName="TextSearch" placeholder="Search">
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
</ul>
</div>
<!-- /.navbar-collapse -->
</div>
<!-- /.container-fluid -->
</nav>
</div>
<div class="clearfix" style="padding-top:10px; padding-bottom: 10px">
<router-outlet></router-outlet>
</div>
<app-footer></app-footer>
<br>
</div>
- You can go through the login div, we are calling the
AuthService
‘sisLoggedIn()
function to check if the user is not logged in (by!
operator), if Yes, we are showing the login button else we are showing the logout template’s div that has logout button with aclick
functionlogout()
fromAuthService
. Also, there would be one more button on top with the title Admin Login to redirect control to the admin home page. So pretty much we have used all methods fromAuthService
now. - Next, edit the src -> app -> app.component.ts and replace its code with the following:
- Just look for
openDialog()
function, we are openingLoginComponent
in a modal popup. That’s the only change we need inAppComponent
in order to make login button functional. - We need two more images in the asset folder. Save these two images and copy them in src -> assets -> images folder:
- That’s it for Part 10 – B, in the upcoming part(s) we will develop the guard to protect the admin section routes, HTTP interceptor to embed the token to API requests,
ErrorComponent
and Search functionality.