VB.Net WebApi OAuth Token Based Authentication + Angular Login Application - Part 2

After a long time studying to take IELTS exam, and it took me a while, let's continue our post about OAuth Token Based Authentication. In this post we are going to build the frontend, which will be built based on Angular - Angular website- and Bulma, a modern CSS framework based on Flexbox - Bulma website

Tools required:
Node.js - Download Node.js website
Angular CLI - A command line interface for Angular. It will be installed by npm, which is a package manager for Node.js.
Visual Studio Code - or another source code editor.

Before we start, check out my DEMO Login Page - user: admin / password: 123456

Go for it!

In Angular 2 "everything is a component". A component can be a page, a button, a navbar, etc. All that we need to build our frontend application are just five components and a service class:
AppComponent - root component which is created automatically by Angular CLI when a new project is generated.
LoginComponent - Login page
HeaderComponent - Contains a navbar items
HomeComponent - Main page
FooterComponent - footer area at the bottom of the page
AuthenticationService - will be configured to communicate with backend services over the HTTP protocol

1 - Installing Angular CLI

Once Node.js is intalled, open a terminal window and run the following command to install Angular CLI globally:

npm install -g angular-cli

2 - Create a new Angular project

Generate a new project by running the following command:

ng new LoginWebApp
cd LoginWebApp

3 - Project structure

All Angular components, templates, styles, images, and anything else will be in the "src" folder. Let's create some subfolders to organize our project.

Under the folder "/src/app", create three subfolders -> /components, /pages and /services
angular project skeleton

4 - Generating Components

As I mentioned, we just need four components - Login, Header, Home and Footer component. Under the folder "/components", create Headbar and Footer running the following commands:

C:\dev\LoginWebApp\src\app\components> ng generate component Headbar
C:\dev\LoginWebApp\src\app\components> ng generate component Footer

Under the folder "/pages", running the following commands:

C:\dev\LoginWebApp\src\app\pages> ng generate component HomePage
C:\dev\LoginWebApp\src\app\pages> ng generate component LoginPage

At this point, we have a component.css, component.html, component.spec.ts and a component.ts files for each component. Let's focus on component.html and component.ts files.
Files ending with "component.ts do the same job as AngularJS controllers, initializing the state and defining the component’s behaviour. So, let's write the html content and implement some functions using Typescript.
Firstly, we are going to include Bulma and Font Awesome references and decorate html tags with Bulma classes. Open the file - /src/index.html
index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>LoginWebApp</title>
  <base href="/">
 
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.6.0/css/bulma.min.css">
</head>
<body>
  <app-root>
    <section class="hero is-dark is-fullheight">
      <div class="hero-body">
        <div class="container has-text-centered">
          <h1 class="title">
            Login WebApp
          </h1>
          <h2 class="subtitle">
            Loading...
          </h2>
        </div>
      </div>
    </section>
  </app-root>
</body>
</html>

Line 15 and 17 - modifier classes from Bulma: "is-dark", "is-fullheight" and "has-text-centered". They all start with "is-" or "has-".

Now, let's implement all html files.
headbar.component.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<section class="hero is-info is-small">
  <div class="hero-head">
      <header class="navbar">
          <div class="container">
              <div class="navbar-left">
                  <a class="navbar-item medium-text">Login WebApp</a>
              </div>
              <span class="navbar-burger burger" data-target="navbarMenuHeroC">
                <span></span>
                <span></span>
                <span></span>
              </span>
              <div id="navbarMenuHeroC" class="navbar-menu">
                <div class="navbar-end">
                  <div class="navbar-menu">
                    <a class="navbar-item" routerLink="/home">Welcome, {{ user }}</a>
                    <a class="navbar-item" routerLink="/home">Home</a>
                    <a class="navbar-item">Profile</a>
                    <span class="navbar-item">
                      <a class="button is-small is-info is-inverted" (click)="logout()">
                          <span class="icon">
                              <i class="fa fa-power-off"></i>
                          </span>
                        <span>Logout</span>
                    </a>
                    </span>
                  </div>
                </div>
              </div>
          </div>
      </header>
  </div>
</section>

footer.component.html

1
2
3
4
5
6
7
8
9
<footer class="footer">
  <div class="container">
    <div class="content has-text-centered">
      <p>
        <strong>Login WebApp</strong> by <a href="http://alexandreomiranda.com/">alexandreomiranda.com</a>.
      </p>
    </div>
  </div>
</footer>

home-page.component.html

1
2
3
4
5
6
7
8
9
10
11
12
<app-headbar></app-headbar>
<section class="section">
    <div class="container">
        <div class="heading">
            <h1 class="title">Main Page</h1>
            <h2 class="subtitle">
                You are on a restricted area
            </h2>
        </div>
    </div>
</section>
<app-footer></app-footer>

login-page.component.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<section class="hero is-dark is-fullheight">
  <div class="hero-body">
    <div class="container has-text-centered">
      <h1 class="title larger-text">
        Login WebApp
      </h1>
      <div class="columns">
        <div class="column is-half is-offset-one-quarter">
          <form [formGroup]="form">
              <div class="field">
                <p class="control has-icons-left has-icons-right">
                  <input class="input" type="text" placeholder="Username" formControlName="username">
                  <span class="icon is-small is-left">
                    <i class="fa fa-user"></i>
                  </span>
                  <span class="icon is-small is-right has-text-success" *ngIf="form.controls.username.valid && !form.controls.username.pristine">
                    <i class="fa fa-check"></i>
                  </span>
                  <span class="icon is-small is-right has-text-danger" *ngIf="!form.controls.username.valid && !form.controls.username.pristine">
                    <i class="fa fa-warning"></i>
                  </span>
                </p>
              </div>
              <div class="field">
                <p class="control has-icons-left has-icons-right">
                  <input class="input" type="password" placeholder="Password" formControlName="password">
                  <span class="icon is-small is-left">
                    <i class="fa fa-lock"></i>
                  </span>
                  <span class="icon is-small is-right has-text-success" *ngIf="form.controls.password.valid && !form.controls.password.pristine">
                    <i class="fa fa-check"></i>
                  </span>
                  <span class="icon is-small is-right has-text-danger" *ngIf="!form.controls.password.valid && !form.controls.password.pristine">
                    <i class="fa fa-warning"></i>
                  </span>
                </p>
              </div>
              <div class="field">
                <p class="control">
                  <button (click)="submit()" class="button is-success" [disabled]="!form.valid">Login</button>
                </p>
              </div>
          </form>
        </div>
      </div>
    </div>
  </div>
</section>

So far, so good!

5 - Building components

Now, we are going to build our components. There is nothing to change in the Footer and Home components, so let's go through the code line-by-line.

home-page.component.ts

1
2
3
4
5
6
7
8
9
10
11
12
import { Component, OnInit } from '@angular/core';
 
@Component({
  selector: 'app-home-page',
  templateUrl: './home-page.component.html',
  styleUrls: ['./home-page.component.css']
})
export class HomePageComponent implements OnInit {
  constructor() { }
  ngOnInit() {
  }
}

Line 1 - We are telling Angular that our class is a component. So, we need to import the component decorator and add it above our class(Line 3). Then, we need to configure two properties - "selector" and "template"(Lines 4 and 5). The property "styleUrls" was included automatically when the component was created and, for now, it will not be used.
Line 8 - We are exporting the class to make it usable by other parts of the code.
Line 9 - constructor() - Default method that is executed when the class is instantiated
Line 10 - ngOnInit() - Called after the constructor - is not mandatory but considered good practice

That being said, let's implement Headbar and Login components:
headbar.component.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
 
@Component({
  selector: 'app-headbar',
  templateUrl: './headbar.component.html'
})
export class HeadbarComponent implements OnInit {
  public user: string = '';
 
  constructor(private router: Router) { 
    var dataClient: any = localStorage.getItem('loginWebApp.user');
    if (dataClient) {
      this.user = dataClient;
    }
  }
 
  ngOnInit() {
  }
 
  logout() {
    localStorage.removeItem('loginWebApp.user');
    localStorage.removeItem('loginWebApp.token');
    this.router.navigateByUrl('/');
  }
}

Line 11 - We are injecting Router component into the constructor. Basically, this is how Dependency Injection works in Angular. Any component is injectable by default.
Lines 12 to 15 - getting current logged username from localStorage to show it in the headbar - "Welcome, {{ user }}"
Line 21 - Removing data from localStorage and redirecting to base route after logout button click event.

login-page.component.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import { Component, OnInit } from '@angular/core';
import { Validators, FormBuilder, FormGroup} from '@angular/forms';
import { DataService } from '../../services/data.service';
import { Router } from '@angular/router';
 
@Component({
  selector: 'app-login-page',
  templateUrl: './login-page.component.html',
  providers: [DataService]
})
export class LoginPageComponent implements OnInit {
  public form: FormGroup;
  public errors: any[] = [];
 
  constructor(private fb: FormBuilder, private dataService: DataService, private router: Router) { 
    var tokenClient = localStorage.getItem('loginWebApp.token');
    if(tokenClient){
      this.router.navigateByUrl('/home');
    }
 
    this.form = this.fb.group({
      username: ['',Validators.compose([
        Validators.minLength(3),
        Validators.maxLength(30),
        Validators.required
      ])],
      password:['',Validators.compose([
        Validators.minLength(6),
        Validators.maxLength(20),
        Validators.required
      ])]
    });
  }
 
  ngOnInit() {
  }
 
  submit(){
    this.dataService
      .authenticate(this.form.value)
      .subscribe(result => {
        localStorage.setItem('loginWebApp.token', result.access_token);
        localStorage.setItem('loginWebApp.user', result.nome);
        this.router.navigateByUrl('/home');
      }, error => {
        if(error.status == 0){
          alert('Server Unavailable... :(')    
        }
        else {
          this.errors = JSON.parse(error._body);
          alert('Status: ' + error.status + ' \nResponse: ' + JSON.stringify(this.errors));
        }
      });
  }
 
}

Line 9 - LoginPage component has its own instance of DataService.
Lines 16 to 18 - If there is a token in the localStorage, the user will be redirected to /home
Lines 21 to 31 - setting validators to username and password inputs
Line 40 - Calling authenticate() method from DataService class, which implements an "Observable Data Service" to make a HTTP request and return a valid token.

6 - Configuring HTTP service

HTTP service in Angular is based on Observables. I really recomend you to read about RxJS and Observable Data Service - HTTP Example with Observables / Angular University - Observables / Todd Motto - Observables

The next step is configure services to send HTTP requests to REST-based services, which was built in the previous post.

Under the folder "/app/services", create two files: authentication.service.ts and data.service.ts

authentication.service.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { Injectable } from '@angular/core';
import { Router, CanActivate } from '@angular/router';
 
@Injectable()
export class AuthenticationService implements CanActivate{
    constructor(private router: Router){
    }
    canActivate(){
        if (!localStorage.getItem('loginWebApp.token')) {
            this.router.navigate(['/']);
            return false;
        }
        return true;
    }
}

Line 4 - A class with @Injectable to tell Angular that it can be injected by other component
Line 8 - This method just check if there is a token and return true or false in order to activate the route(Check out below app.routing.ts - line 10)

data.service.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { Injectable } from '@angular/core';
import { Http, Response, Headers, RequestOptions} from '@angular/http';
import 'rxjs/add/operator/map';
import { Observable } from 'rxjs/Observable';
 
@Injectable()
export class DataService{
    private serviceUrl: string = 'http://www.alexandreomiranda.com/services';
    constructor(private http: Http) {}
 
    authenticate(data: any){
        var dt = "grant_type=password&username=" + data.username + "&password=" + data.password;
        let headers = new Headers({ 'Content-Type': 'application/x-www-form-urlencoded' }); 
        let options = new RequestOptions({ headers: headers });
        return this.http.post(this.serviceUrl + '/api/security/token', dt, options).map((res: Response) => res.json());
    } 
}

Line 15 - Call map on the response observable to get the parsed object in JSON format

Let's configure our routes. Under the folder "/app" create a file called "app.routing.ts"
app.routing.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
import { ModuleWithProviders } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
 
import { HomePageComponent } from './pages/home-page/home-page.component';
import { LoginPageComponent } from './pages/login-page/login-page.component';
import { AuthenticationService } from './services/authentication.service';
 
const routes: Routes = [
    { path: '', component: LoginPageComponent },
    { path: 'home', canActivate: [AuthenticationService],component: HomePageComponent },
];
 
export const Routing: ModuleWithProviders = RouterModule.forRoot(routes);

Line 10 - /home route will be activated if canActivate function returns true.

app.component.html

1
<router-outlet></router-outlet>

Line 1 - Whenever we switch routes, the content will be rendered in place of the router-outlet tag

Finally, import all components in app.module.ts

app.module.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
 
import { AppComponent } from './app.component';
import { HeadbarComponent } from './components/headbar/headbar.component';
import { FooterComponent } from './components/footer/footer.component';
import { HomePageComponent } from './pages/home-page/home-page.component';
import { LoginPageComponent } from './pages/login-page/login-page.component';
 
import { Routing } from './app.routing';
import { AuthenticationService } from './services/authentication.service';
 
@NgModule({
  declarations: [
    AppComponent,
    HeadbarComponent,
    FooterComponent,
    HomePageComponent,
    LoginPageComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    ReactiveFormsModule,
    HttpModule,
    Routing
  ],
  providers: [AuthenticationService],
  bootstrap: [AppComponent]
})
export class AppModule { }

Line 30 - Single instance of AuthenticationService which will be shared among components. (Singleton Services shall only be kept in app.module.ts)

That's all!

7 - Running the application

Open a terminal window and run the following command:

C:\dev\LoginWebApp\> ng serve

"ng serve" builds the application and starts a web server. Default port is 4200. Open the brower and go to http://localhost:4200/
ng serve running

login page angular

If everything is ok, after post a valid username and password, a valid token will be added to localStorage and you will be redirected to home page. The localStorage object stores data with no expiration date. On the other hand, the token remains valid for only three days as we configured in our API in the previous post. Another option is to store the token in the sessionStorage, which stores data for only one session (the data is deleted when the browser tab is closed).

Home - Logged User
home page logged user

Token - localStorage
token

The next post we are going to build an API with Asp.Net Core 2.0 and implement a CRUD App with Angular.

Thanks for reading!

One thought on “VB.Net WebApi OAuth Token Based Authentication + Angular Login Application - Part 2

  1. This is more interesting, thanks for gathering such a great thing, appreciating, here router customer support that gives you more effective service on their behalf, just dial our number and get best technical help from our side.

Leave a Reply

Your email address will not be published. Required fields are marked *

This blog is kept spam free by WP-SpamFree.