Labels

Java (10) Spring (10) Spring MVC (6) Web Services (5) Rest (4) Javascript (3) Nodejs (3) Spring Batch (3) Angular (2) Angular2 (2) Angular6 (2) Expressjs (2) Passportjs (2) SOAP (2) SOAPUI (2) Spring Boot (2) AJAX (1) H2 (1) JQuery (1) JUnit (1) Npm (1) Puppeteer (1) Python (1) RaspberryPi (1) Raspbian (1) SQL (1) SQLite (1) Scripts (1) html (1)

Tuesday, November 13, 2018

JWT authentification with passportjs and angular 6 - Part 2 frontend

In this post we will continue with the work in Part 1:

https://code-flow-hjbello.blogspot.com/2018/11/jwt-authentification-with-passportjs.html

In the previous chapter we build the backend using nodejs (passportjs and expressjs). Now we will continue with the frontend in angular 6. You can download the code here

PART2. Angular 6 project

To start a new angular project we will use the angular cli. If we do not have in installed we just use:

npm install -g @angular/cli

then we create the app with:

ng new angular6-secured-app




We will need to install bootstrap because we will use it in our application, so we will install it using


npm install --save bootstrap

also, inside the styles.css file we will add the line:

@import "~bootstrap/dist/css/bootstrap.css";

We will asume that we have our backend running on localhost:3000.

Authentification services


We will start creating the angular services that will communicate with the backend to login and register a new user

we will create the following services and files using "ng g service (...)"



Let us see how they work

auth.service.ts

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { LoginData, UserRetrieved, NewUser } from './user';
import { Subject } from 'rxjs';
import { Router } from '@angular/router';
@Injectable({
  providedIn: 'root'
})
export class AuthService {

  constructor(private http: HttpClient, private router: Router) { }
  urlBackend = 'http://localhost:3000';
  loggedUser: string;
  loggedUser$: Subject<string>; = new Subject();


  async login(loginData: LoginData) {
    const data: any = await this.http.post(this.urlBackend + '/security/login', loginData)
    .toPromise();
    localStorage.setItem('jwtToken', data.token);
    this.loggedUser = loginData.username;
    this.loggedUser$.next(this.loggedUser);
  }

  async signup(signupData: NewUser) {
    return await this.http.post(this.urlBackend + '/security/signup', signupData).toPromise();
  }
  async logout() {
    this.loggedUser = null;
    this.loggedUser$.next(this.loggedUser);
    this.router.navigate(['/']);

  }
}

As you can see, we are using the angular http module to access the login endpoint and storage the token. We are saving the token in the local storage when we do this:

...
localStorage.setItem('jwtToken', data.token);
...

To sign up we are are posting the new user data in the right route too.

We are using the observable loggedUser$ to publish when the user has changed.

We also add an auth guard, that will be used to redirect the user to another route if the user is not authorized:

auth-guard.service.ts

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { AuthService } from './auth.service';
import { Router } from '@angular/router';

@Injectable()
export class AuthGuard implements CanActivate {

  constructor(private authService: AuthService, private router: Router) { }

  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
    if (!this.authService.loggedUser) {
      this.router.navigate(['/']);
      return false;
    }
    return true;
  }
}


we add the typescript file user.ts to create various interfaces to pass the user data:

export interface LoginData {
    username: string;
    password: string;
}

export interface UserRetrieved {
    username: string;
    token: string;
}
export interface NewUser {
    password: string;
    username: string;
    email: string;
}


Router module and routes

We will need a router module to configure the routes in our app. We will add routes for login, signup and entries. We will also need to add the AuthGuard class in the providers, so that it redirects the user to home in case the user is not authenticated.

app-routing.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Route } from '@angular/compiler/src/core';
import { LoginComponent } from './login/login.component';
import { SingupComponent } from './singup/singup.component';
import { NewEntryComponent } from './new-entry/new-entry.component';
import { RouterModule } from '@angular/router';
import { UserPageComponent } from './user-page/user-page.component';
import { AuthGuard } from './auth/auth-guard.service';
import { EntriesComponent } from './entries/entries.component';



const routes = [
  {
    path: '',
    redirectTo: 'login',
    pathMatch: 'full'
  },
  {
    path: 'login',
    component: LoginComponent
  },
  {
    path: 'signup',
    component: SingupComponent
  },
  {
    path: 'new_entry',
    component: NewEntryComponent,
    canActivate: [
      AuthGuard
    ]
  },
  {
    path: 'entries',
    component: EntriesComponent,
  },
  {
    path: 'user_page',
    component: UserPageComponent,
    canActivate: [
      AuthGuard
    ]
  }
];

@NgModule({
  imports: [
    CommonModule, RouterModule.forRoot(routes)
  ],
  exports: [RouterModule],
  providers: [AuthGuard],
  declarations: []
})


export class AppRoutingModule { }

Blog entries service


We will invoke the backend and retrieve the blog data using the following EntriesService:

entries.service.ts

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})
export class EntriesService {

  constructor(private http: HttpClient) { }
  urlBackend = 'http://localhost:3000';


  async getEntry(title: string): Promise<Object> {
    const httpOptions = {
      headers: new HttpHeaders({ 'Authorization': localStorage.getItem('jwtToken') })
    };
    return await this.http.get(this.urlBackend + '/titled/' + title, httpOptions)
   .toPromise();
  }

  async getEntries(limit: number, skip: number): Promise<any> {
    return await this.http.get(this.urlBackend + '/entries/entries_list/limit=' + 
                                             limit + '&skip=' + skip).toPromise();
  }

  async saveEntry(entry: BlogEntry): Promise<Object> {
    const httpOptions = {
      headers: new HttpHeaders({ 'Authorization': localStorage.getItem('jwtToken') })
    };
    return await this.http.post(this.urlBackend + '/entries/save_entry', entry, 
                      httpOptions).toPromise();
  }

}

export interface BlogEntry {
  title: string;
  content: string;
  tags: string;
}


Notice that we are adding a token into the http header when we do:

... 
const httpOptions = {
      headers: new HttpHeaders({ 'Authorization': localStorage.getItem('jwtToken') })
    };
...

this token will is storaged in localStorage, and is saved there when the user logs in (we did this in auth.service.ts)

Login and Signup Components

We will create a component to log in into the application

login.component.ts

import { Component, OnInit } from '@angular/core';
// import { Router } from "@angular/router";
import { Observable } from 'rxjs';
import { tap, catchError } from 'rxjs/operators';
import { Router } from '@angular/router';
import { LoginData } from '../auth/user';
import { AuthService } from '../auth/auth.service';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {

  constructor(private router: Router, private authService: AuthService) { }
  loginData: LoginData = { username: '', password: '' };
  message = '';
  data: any;

  ngOnInit() {
  }

  async login() {
    try {
      await this.authService.login(this.loginData);
      this.router.navigate(['user_page']);
      this.message = '';
    } catch (err) {
      this.message = err.error.msg;
    }
  }

}

login.component.html


<div class="form-signin custom-login">
  <div class="alert alert-warning alert-dismissible" role="alert" 
*ngIf="message !== ''">
    <button type="button" class="close" data-dismiss="alert" 
aria-label="Close"><span aria-hidden="true">�-</span></button>
    {{message}}
  </div>
  <h2 class="form-signin-heading">Log in</h2>
  <label for="inputUsername" class="sr-only">Email address</label>
  <input type="text" class="form-control" 
placeholder="username" [(ngModel)]="loginData.username" name="username"
    required />
  <label for="inputPassword" class="sr-only">Password</label>
  <input type="password" class="form-control" placeholder="Password" 
[(ngModel)]="loginData.password" name="password"
    required />
  <button class="btn btn-lg btn-primary btn-block" 
(click)="login()">Log in</button>
  <div>
    <a routerLink='/signup'>sign up</a>
  </div>
</div>


When the user clicks log in, we invoke the login service and pass the user and password. If the operation is successful we navigate to user_page


This is how it will look like:




Now let us see how to register new users:

singup.component.ts

import { Component, OnInit } from '@angular/core';
import { AuthService } from '../auth/auth.service';
import { Router } from '@angular/router';
import { NewUser } from '../auth/user';

@Component({
  selector: 'app-singup',
  templateUrl: './singup.component.html',
  styleUrls: ['./singup.component.css']
})
export class SingupComponent implements OnInit {

  constructor(private router: Router, private authService: AuthService) { }
  newUser: NewUser = { username: '', password: '', email: '' };
  message = '';
  ngOnInit() {
  }

  async signup() {
    try {
      await this.authService.signup(this.newUser);
      this.router.navigate(['/user_page']);
    } catch (err) {
      this.message = err.error.msg;
    }

  }

}


signup.component.html


<div class="form-signin custom-signup">
  <div class="alert alert-warning alert-dismissible" role="alert" *ngIf="message !== ''">
    <button type="button" class="close" data-dismiss="alert" aria-label="Close">
     <span aria-hidden="true">&times;</span></button>
    {{message}}
  </div>

  <h2 class="form-signin-heading">Create an account</h2>
  <label for="inputEmail" class="sr-only">Email address</label>
  <input type="email" class="form-control" placeholder="Email address" 
     [(ngModel)]="newUser.email" name="email" />
  <input type="text" class="form-control" placeholder="User name" 
     [(ngModel)]="newUser.username" name="username"
    required />
  <label for="inputPassword" class="sr-only">Choose a password</label>
  <input type="password" class="form-control" placeholder="Password" 
     [(ngModel)]="newUser.password" name="password"
    required />
  <button class="btn btn-lg btn-primary btn-block" (click)="signup()">Sign Up</button>
  <div>
    <a routerLink='/login'>login instead</a>
  </div>
</div>


This is how it looks like:



The behaviour is quite simple, we just pass the data to the service and redirect to another page if everything goes ok.

navbar component

Let us take a look into the navigation bar

import { Component, OnInit, OnDestroy } from '@angular/core';
import { AuthService } from '../auth/auth.service';
import { Subscription } from 'rxjs';

@Component({
  selector: 'app-navbar',
  templateUrl: './navbar.component.html',
  styleUrls: ['./navbar.component.css']
})
export class NavbarComponent implements OnInit, OnDestroy {
  loggedUser: string;
  loggedUserSubs: Subscription;
  constructor(private authService: AuthService) { }

  ngOnInit() {
    this.loggedUser = this.authService.loggedUser;
    this.loggedUserSubs = this.authService.loggedUser$.subscribe((loggedUser) => {
      this.loggedUser = loggedUser;
    });
  }

  ngOnDestroy() {
    this.loggedUserSubs.unsubscribe();
  }

  logout() {
    this.authService.logout();
  }

}


When the component is created we subscribe to the observable loggedUser from the service authService that we have injected. Doing this we update the navbar if the user logs in. This will allow us to show the current user in the navigation bar.

The navbar html is built with bootstrap:

<nav class="navbar navbar-dark bg-dark mb-5">
  <a class="navbar-brand" href="/">Secured App</a>
  <div class="navbar-expand mr-auto">
    <div class="navbar-nav">
      <a class="nav-item nav-link" routerLink="/entries" 
       routerLinkActive="active">Entries</a>
      <a class="nav-item nav-link" routerLink="/new_entry" 
       routerLinkActive="active">New entry</a>
      <a class="nav-item nav-link" routerLink="/user_page" 
       routerLinkActive="active">user page</a>
    </div>
  </div>
  <div class="navbar-expand ml-auto navbar-nav">
    <div class="navbar-nav">
      <span *ngIf="loggedUser" class="nav-item nav-link">
        {{loggedUser}}
      </span>
      <a *ngIf="loggedUser" class="nav-item nav-link" (click)="logout()">
        logout
      </a>
      <a *ngIf="!loggedUser" class="nav-item nav-link" routerLink="/login" 
       routerLinkActive="active">
        login
      </a>
      <a class="nav-item nav-link" routerLink="/signup" routerLinkActive="active">
        signup
      </a>
    </div>
  </div>
</nav>

This is our navbar:


When the user logs in it will appear in the left corner:


new-entry component

We will also have a component to publish new posts. This component will use our entries service to send data to the back end.

new-entry.component.ts

import { Component, OnInit } from '@angular/core';
import { BlogEntry, EntriesService } from '../entries.service';
import { Router } from '@angular/router';

@Component({
  selector: 'app-new-entry',
  templateUrl: './new-entry.component.html',
  styleUrls: ['./new-entry.component.css']
})
export class NewEntryComponent implements OnInit {

  constructor(private router: Router, private entriesService: EntriesService) { }
  entry: BlogEntry = { title: 'enter title', content: 'enter content', tags: 'enter tags' };
  ngOnInit() {
  }
  async submmit() {
    await this.entriesService.saveEntry(this.entry);
    this.router.navigate(['entries']);

  }

}

new-entry.component.ts

<label for="title" class="sr-only">Title</label>
<input type="text" class="form-control" placeholder="title" [(ngModel)]="entry.title" 
name="title" required />
<label for="tags" class="sr-only">Tags</label>
<input type="text" class="form-control" placeholder="tags" [(ngModel)]="entry.tags"
 name="tags" required />
<div class="form-group">
  <label for="content">Content</label>
  <textarea class="form-control rounded-0" id="content" rows="10" 
[(ngModel)]="entry.content"></textarea>
</div>
<button class="btn btn-primary" (click)="submmit()">Send</button>


when the user clicks the button we send the two text fields in the form of a BlogEntry to the entriesService. Then the entries service sends the data adding the token into the heading.


This is how it looks like:



entries component

entries.component.ts


import { Component, OnInit } from '@angular/core';
import { EntriesService, BlogEntry } from '../entries.service';

@Component({
  selector: 'app-entries',
  templateUrl: './entries.component.html',
  styleUrls: ['./entries.component.css']
})
export class EntriesComponent implements OnInit {

  constructor(private entriesService: EntriesService) { }
  entries: BlogEntry[];
  async ngOnInit() {
    this.entries = await this.entriesService.getEntries(10, 0);
    console.log(this.entries);
  }

}

entries.component.html

<h2>Entries</h2>
<div class="card" *ngFor="let entry of entries">
  <div class="card-title"><strong>{{entry.title}}</strong></div>
  <div class="card-body">{{entry.content}}</div>
</div>

This component just retrieves all the entries using entriesService. This does not require authentification.

This is how it looks:




Download the code:



No comments:

Post a Comment