Angular2 Token Based Authentication using ASP.NET Core Web API and JSON Web Token

ASP.NET Core Identity is designed to enable us to easily use a number of different storage providers for our ASP.NET applications. We can use the supplied identity providers that are included with the .NET Framework, or we can implement our own providers.

In this tutorial, we will build a Token-Based Authentication using ASP.NET Core Identity , ASP.NET Core Web API and Angular

With Token-Based Authentication, the client application is not dependent on a specific authentication mechanism. The token is generated by the server and the Web API have some APIs to understand, validate the token and perform the authentication. This approach provides Loose Coupling between client and the Web API.

this toturial is not for beginners, to follow it, you must understand Angular2 and Asp.NET REST Services

Securing our web application consists of two scenarios : Authentication and Authorization

1. Authenticationidentifies the user. So the user must be registered first, using login and password or third party logins like Facebook, Twitter, etc…

2. Authorization talks about permission for authenticated users

– What is the user (authenticated) allowed to do ?

– What ressources can the user access ?

We have build our back end service using ASP.NET WEB API Core, web api provides an internal authorization server using OWIN MIDDLEWARE

The authorization server and the authentication filter are both called into an OWIN middleware component that handles OAuth2

This article is the third part of a series of 3 articles

  1. Token Based Authentication using Asp.net Core Web Api
  2. Asp.Net Core Web Api Integration testing using EntityFrameworkCore LocalDb and XUnit2
  3. Angular Token Based Authentication using Asp.net Core Web API and JSON Web Token

BUILDING WEB API RESSOURCE SERVER AND AUTHORIZATION SERVER

In the first part Token Based Authentication using Asp.net Core Web API , I talked about how to configure an ASP.NET Web API Core Token Based Authentication using JWT. So in this tutorial I will talk about an Angular2 client that connect to the Web Api Authorization server using a JWT Token

BUILDING ANGULAR2 WEB CLIENT

Create an ASP.NET Empty WebSite and structure it as follow

Package.json

 "dependencies": {
    "@angular/common": "~2.4.0",
    "@angular/compiler": "~2.4.0",
    "@angular/core": "~2.4.0",
    "@angular/forms": "~2.4.0",
    "@angular/http": "~2.4.0",
    "@angular/platform-browser": "~2.4.0",
    "@angular/platform-browser-dynamic": "~2.4.0",
    "@angular/router": "~3.4.0",
    "systemjs": "0.19.40",
    "angular2-jwt": "^0.2.3",
    "bootstrap": "^3.3.7",
    "core-js": "^2.4.1",
    "rxjs": "^5.0.1",
    "zone.js": "^0.7.4"
  },

Package.json defines some dependencies , so use npm to restore them : npm install

Main.ts

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
 
import { AppModule } from './app.module';
 
platformBrowserDynamic().bootstrapModule(AppModule);

Here, we import AppModule and bootstrap it using platformBrowserDynamic . Because we run our application on the browser. platformBrowserDynamic use a specific way of bootstrapping the application and is defined in @angular/platform-browser-dynamic

There are many ways to setup an angular2 project (Webpack or System.js), but we can use the angular project template in visual studio 2017

app.module.ts file

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpModule } from '@angular/http';
import { ReactiveFormsModule } from '@angular/forms';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HomeComponent } from './home/home.component';
import { PageNotFoundComponent } from './page-not-found.component';
 
import { UserModule } from './user/user.module';
import { CommonService } from './shared/common.service';
 
@NgModule({
    imports: [
        BrowserModule,
        HttpModule,
        ReactiveFormsModule,
        UserModule,
        AppRoutingModule
    ],
    declarations: [
        AppComponent,
        HomeComponent,
        PageNotFoundComponent
    ],
    providers: [
        CommonService
    ],
    bootstrap: [AppComponent]
})
export class AppModule { }

app.module contains sections like :

  • Imports : import dependencies
  • declarations : components, …
  • providers : injected services

index.html


    document.write('');
    AngularJS 2 Token based Authentication using Asp.net Core Web API and JSON Web Token
    
    
    
    
    
 
    
 
    
    
    
    
    
        System.import('app/main.js').catch(function (err) { console.error(err); });
    


    Starting Client Application, please wait ...


Index.html is the entry point of our SPA application, the selector jwt-app will be replaced dynamically by the template of the current route

app.component.ts

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { IProfile } from './user/user.model';
import { UserService } from './user/user.service';
import { UserProfile } from './user/user.profile';
 
@Component({
    selector: 'jwt-app',
    templateUrl: './app/app.component.html'
})
export class AppComponent implements OnInit {
    pageTitle: string = 'Welcome to AngularJS 2 Token based Authentication using Asp.net Core Web API and JSON Web Token';
    loading: boolean = true;
    Profile: IProfile;
 
    constructor(private authService: UserService,
        private authProfile: UserProfile,
        private router: Router) {
    }
    ngOnInit(): void {
        this.Profile = this.authProfile.userProfile;
    }
 
    logOut(): void {
        this.authService.logout();
 
        this.router.navigateByUrl('/home');
    }
}

common.service.ts

Lets create a common.service.ts file and CommonService class and decorate it as @Injectable()

import { Injectable } from ‘@angular/core’;

import { Injectable } from '@angular/core';
import { Observable } from "rxjs/Observable";
import 'rxjs/add/observable/throw';
import { Response } from '@angular/http';
 
@Injectable()
export class CommonService {
    private baseUrl = 'http://localhost:58834/api';
 
    constructor() { }
 
    getBaseUrl(): string {
        return this.baseUrl;
    }
 
    handleFullError(error: Response) {
        return Observable.throw(error);
    }
 
    handleError(error: Response): Observable {
        let errorMessage = error.json();
        console.error(errorMessage);
        return Observable.throw(errorMessage.error || 'Server error');
    }
}

Here, we implement common functionalities such as error handling, baseUrl to call API, (http://localhost:58834/api is the API endpoint)

app-routing.module.ts

import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { PageNotFoundComponent } from './page-not-found.component';
import { AuthGuard } from './common/auth.guard';
 
@NgModule({
    imports: [
        RouterModule.forRoot([
            { path: 'home', component: HomeComponent },
            {
                path: 'products',
                canActivate: [AuthGuard],
                data: { preload: true },
                loadChildren: 'app/products/product.module#ProductModule'
            },
            { path: '', redirectTo: 'home', pathMatch: 'full' },
            { path: '**', component: PageNotFoundComponent }
        ], { useHash: true })
    ],
 
    exports: [RouterModule]
})
export class AppRoutingModule { }

app-routing module enable us to define routing strategy :

  • http://localhost:3276/home : loads HomeComponent
  • Empty URL : loads HomeComponent also
  • No existing URL : loads PageNotFoundComponent

app.component.html


home.component.ts

import { Component } from '@angular/core';
 
@Component({
    templateUrl: './app/home/home.component.html'
})
export class HomeComponent {
    public pageTitle: string = 'Welcome';
}

home.component.html

{{pageTitle}}
export interface IProduct {
    id: number;
    name: string;
    description: string;
    price: number;
}

product.service.ts

import { Injectable } from '@angular/core';
import { Http, Response, Headers, RequestOptions } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/map';
import 'rxjs/add/observable/of';
import { IProduct } from './product.model';
import { UserProfile } from '../user/user.profile'
import { CommonService } from '../shared/common.service'
 
@Injectable()
export class ProductService {
    constructor(private http: Http,
        private authProfile: UserProfile,
        private commonService: CommonService) { }
 
    getProducts(): Observable {
        let url = this.commonService.getBaseUrl() + '/product';
 
        let options = null;
        let profile = this.authProfile.getProfile();
 
        if (profile != null && profile != undefined) {
            let headers = new Headers({ 'Authorization': 'Bearer ' + profile.token });
            options = new RequestOptions({ headers: headers });
        }
        let data: Observable = this.http.get(url, options)
            .map(res => res.json())
            .do(data => console.log('getProducts: ' + JSON.stringify(data)))
            .catch(this.commonService.handleError);
 
        return data;
    }
}

ProductService.getProducts is the protected ressource, so to get products users must be logged in first and get a token in the response. and finally the token is send (in the header) with the request to access protected ressource:

if (profile != null && profile != undefined) {
            let headers = new Headers({ 'Authorization': 'Bearer ' + profile.token });
            options = new RequestOptions({ headers: headers });

ProductListComponent is protected by a guard. So we must add a guard to any protected route. Here AuthGuard.canActivate must be equal to true but AuthGuard.canActivate return true only if the user is authenticated and the token is valid.

The server validate the token and send good response if the user is authorized ,

RouterModule.forRoot([
            { path: 'home', component: HomeComponent },
            {
                path: 'products',
                canActivate: [AuthGuard],
                data: { preload: true },
                loadChildren: 'app/products/product.module#ProductModule'
            },
            { path: '', redirectTo: 'home', pathMatch: 'full' },
            { path: '**', component: PageNotFoundComponent }
        ], { useHash: true })

product-list.component.ts

import { Component, OnInit } from '@angular/core';
 
import { IProduct } from './product.model';
import { ProductService } from './product.service';
 
@Component({
    templateUrl: './app/products/product-list.component.html',
    styleUrls: ['./app/products/product-list.component.css']
})
export class ProductListComponent implements OnInit {
    pageTitle: string = 'Product List';
    products: IProduct[];
 
    constructor(private productService: ProductService) { }
 
    ngOnInit(): void {
        this.productService.getProducts()
            .subscribe(products => this.products = products,
            error => console.log(error));
    }
}

product-list.component.html

{{pageTitle}}
Id Product Price
{{product.id}} {{product.name}} {{ product.price }}
  1. Register User

signup.component.ts

import { Component } from '@angular/core';
import { NgForm } from '@angular/forms';
import { Router } from '@angular/router';
import { Http, Response, Headers, RequestOptions } from '@angular/http';
 
import { UserService } from '../user/user.service';
 
@Component({
    templateUrl: './app/signup/signup.component.html'
})
export class SignupComponent {
    errorMessage: string;
    pageTitle = 'signup';
 
    constructor(private authService: UserService,
        private router: Router) { }
 
    register(signupForm: NgForm) {
        if (signupForm && signupForm.valid) {
            let userName = signupForm.form.value.userName;
            let password = signupForm.form.value.password;
            let confirmPassword = signupForm.form.value.confirmPassword;
            var result = this.authService.register(userName, password, confirmPassword)
                .subscribe(
                response => {
                    if (this.authService.redirectUrl) {
                        this.router.navigateByUrl(this.authService.redirectUrl);
                    } else {
                        this.router.navigate(['/']);
                    }
                },
                error => {
                    var results = error['_body'];
                    this.errorMessage = error.statusText + ' ' +
 
                        error.text();
                }
                );
        } else {
            this.errorMessage = 'Please enter a user name and password.';
        };
    }
}

signup.component.html

{{pageTitle}}
User name is required.
Password is required.
confirmPassword is required.
Cancel
{{errorMessage}}

2. Authenticate User

login.component.ts

import { Component } from '@angular/core';
import { NgForm } from '@angular/forms';
import { Router } from '@angular/router';
import { UserService } from '../user/user.service';
 
@Component({
    templateUrl: './app/login/login.component.html'
})
export class LoginComponent {
    errorMessage: string;
    pageTitle = 'Log In';
 
    constructor(private authService: UserService,
        private router: Router) { }
 
    login(loginForm: NgForm) {
        if (loginForm && loginForm.valid) {
            let userName = loginForm.form.value.userName;
            let password = loginForm.form.value.password;
            var result = this.authService.login(userName, password).subscribe(
                response => {
                    if (this.authService.redirectUrl) {
                        this.router.navigateByUrl(this.authService.redirectUrl);
                    } else {
                        this.router.navigate(['/products']);
                    }
                },
                error => {
                    var results = error['_body'];
                    this.errorMessage = error.statusText + ' ' +
 
                        error.text();
                });
        } else {
            this.errorMessage = 'Please enter a user name and password.';
        };
    }
}

login.component.html

{{pageTitle}}
User name is required.
Password is required.
Cancel
{{errorMessage}}

3. Auth Gard

auth.guard.ts

import { Injectable } from '@angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { UserService } from '../user/user.service';
 
@Injectable()
export class AuthGuard implements CanActivate {
    constructor(private router: Router, private authService: UserService) { }
 
    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
        if (this.authService.isAuthorized()) {
            return true;
        }
 
        this.authService.redirectUrl = state.url;
        this.router.navigate(['/login']);
        return false;
    }
}

user.model.ts

export interface IProfile {
    token: string;
    expiration: string;
    claims: IClaim[];
    currentUser: IUser;
}
 
interface IClaim {
    type: string;
    value: string;
}
 
interface IUser {
    id: string;
    userName: string;
    email: string;
}

user.module.ts

import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { LoginComponent } from '../login/login.component';
import { SignupComponent } from '../signup/signup.component';
import { AuthGuard } from '../common/auth.guard';
import { SharedModule } from '../shared/shared.module';
 
import {
    UserService,
    UserProfile
} from './index'
 
@NgModule({
    imports: [
        SharedModule,
        RouterModule.forChild([
            { path: 'login', component: LoginComponent },
            { path: 'signup', component: SignupComponent },
        ])
    ],
    declarations: [
        LoginComponent,
        SignupComponent
    ],
    providers: [
        UserService,
        AuthGuard,
        UserProfile
    ]
})
export class UserModule { }

user.profile.ts

import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { Headers } from '@angular/http';
 
import {
    IProfile
} from './index'
 
@Injectable()
export class UserProfile {
    userProfile: IProfile = {
        token: "",
        expiration: "",
        currentUser: { id: '', userName: '', email: '' },
        claims: null
    };
    constructor(private router: Router) {
    }
 
    setProfile(profile: IProfile): void {
        var nameid = profile.claims.filter(p => p.type == 'nameid')[0].value;
        var userName = profile.claims.filter(p => p.type == 'sub')[0].value;
        var email = profile.claims.filter(p => p.type == 'email')[0].value;
        sessionStorage.setItem('access_token', profile.token);
        sessionStorage.setItem('expires_in', profile.expiration);
        sessionStorage.setItem('nameid', nameid);
        sessionStorage.setItem('userName', userName);
        sessionStorage.setItem('email', email);
    }
 
    getProfile(authorizationOnly: boolean = false): IProfile {
        var accessToken = sessionStorage.getItem('access_token');
 
        if (accessToken) {
            this.userProfile.token = accessToken;
            this.userProfile.expiration = sessionStorage.getItem('expires_in');
            if (this.userProfile.currentUser == null) {
                this.userProfile.currentUser = { id: '', userName: '', email: '' }
            }
            this.userProfile.currentUser.id = sessionStorage.getItem('nameid');
            this.userProfile.currentUser.userName = sessionStorage.getItem('userName');
        }
 
        return this.userProfile;
    }
 
    resetProfile(): IProfile {
        sessionStorage.removeItem('access_token');
        sessionStorage.removeItem('expires_in');
        this.userProfile = {
            token: "",
            expiration: "",
            currentUser: null,
            claims: null
        };
        return this.userProfile;
    }
}

user.service.ts

import { Injectable } from '@angular/core';
import { Http, Headers, RequestOptions, Response } from '@angular/http';
import { Router } from '@angular/router';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/catch';
 
import { CommonService } from '../shared/common.service';
import { contentHeaders } from '../common/headers';
import { UserProfile } from './user.profile';
import { IProfile } from './user.model';
 
@Injectable()
export class UserService {
    redirectUrl: string;
    errorMessage: string;
    constructor(
        private http: Http,
        private router: Router,
        private authProfile: UserProfile,
        private commonService: CommonService) { }
 
    isAuthenticated() {
        let profile = this.authProfile.getProfile();
        var validToken = profile.token != "" && profile.token != null;
        var isTokenExpired = this.isTokenExpired(profile);
        return validToken && !isTokenExpired;
    }
    isAuthorized() {
        let profile = this.authProfile.getProfile();
        var validToken = profile.token != "" && profile.token != null;
        var isTokenExpired = this.isTokenExpired(profile);
        return validToken && !isTokenExpired;
    }
    isTokenExpired(profile: IProfile) {
        var expiration = new Date(profile.expiration)
        return expiration  {
                var userProfile: IProfile = response.json();
                this.authProfile.setProfile(userProfile);
                return response.json();
            }).catch(this.commonService.handleFullError);
    }
    register(userName: string, password: string, confirmPassword: string) {
        if (!userName || !password) {
            return;
        }
        let options = new RequestOptions(
            { headers: contentHeaders });
 
        var credentials = {
            email: userName,
            password: password,
            confirmPassword: confirmPassword
        };
        let url = this.commonService.getBaseUrl() + '/auth/register';
        return this.http.post(url, credentials, options)
            .map((response: Response) => {
                return response.json();
            }).catch(this.commonService.handleFullError);
    }
 
    logout(): void {
        this.authProfile.resetProfile();
    }
}

Source Code is available here

SEE IT IN ACTION

Open Package Manager Console and run update-database command to generate database

This is the generated database on localdb

Configure Startup multiple project : TokenAuthWebApiCore.Server and TokenAuthWebApiCore.Client

Run F5

http://logcorner.com/wp-content/uploads/2017/09/AngularToken.webm

Related Posts

Post Views: 718

稿源:ASP.NET Daily Articles (源链) | 关于 | 阅读提示

本站遵循[CC BY-NC-SA 4.0]。如您有版权、意见投诉等问题,请通过eMail联系我们处理。
酷辣虫 » 前端开发 » Angular2 Token Based Authentication using ASP.NET Core Web API and JSON Web Token

喜欢 (0)or分享给?

专业 x 专注 x 聚合 x 分享 CC BY-NC-SA 4.0

使用声明 | 英豪名录