Authentication With Angular 2 App (Front-end) And ASP.NET Web API (Back-end)

In this article, I want to show you an example of how the authentication can be implemented using the ASP.NET Web API as a back-end and Angular 2 as the front-end technology.

For this purpose, I’m going to use an already implemented application and show you just most important pieces of this.
 
First, let’s take a look at the front-end. In app.module, we will register a new provider which is going to be responsible for providing information about authentication (checking whether user is logged in or not) and also for performing the authentication. This is the example of the code.
 
Latest Update In 2017MBuild Alpha Release .Net Core Tools in Visual Studio 2017- The new release of .Net Core Tools based on MSBuild or .Net Core can be tried in Mac Visual Studio or Visual Studio version 2017 RC or at commandline. These tools can run in both .Net Core 1.1 and 1.0 versions.
  1. providers: [  
  2.       AuthGuard,  
  3.       AuthenticationService,  
  4.       IdentityService,  
  5.       {  
  6.           provide: AuthHttp,  
  7.           useFactory: getAuthHttp,  
  8.           deps: [Http]  
  9.       },  
  10. First lets take a look at the IdentityService, this is the code for it:  
  11. import { Injectable } from '@angular/core';  
  12. import { Http, Headers, RequestOptions, Response } from '@angular/http';  
  13. import { Observable } from 'rxjs/Observable';  
  14. import 'rxjs/add/operator/map';  
  15. import 'rxjs/add/operator/catch';  
  16. import 'rxjs/add/observable/throw';  
  17.   
  18. import { AuthHttp } from 'angular2-jwt';  
  19.   
  20. /** 
  21.  * Identity service (to Identity Web API controller). 
  22.  */  
  23. @Injectable()   
  24. export class IdentityService {  
  25.   
  26.     headers: Headers;  
  27.     options: RequestOptions;  
  28.   
  29.     constructor(private authHttp: AuthHttp, private http: Http) {  
  30.   
  31.         // Creates header for post requests.  
  32.         this.headers = new Headers({ 'Content-Type''application/json' });  
  33.         this.options = new RequestOptions({ headers: this.headers });  
  34.   
  35.     }  
  36.   
  37.     /** 
  38.      * Gets all users through AuthHttp. 
  39.      */  
  40.     public GetAll(): Observable<any> {  
  41.   
  42.         // Sends an authenticated request.  
  43.         return this.authHttp.get("/api/identity/GetAll")  
  44.             .map((res: Response) => {  
  45.   
  46.                 return res.json();  
  47.   
  48.             })  
  49.             .catch((error: any) => {  
  50.   
  51.                 // Error on get request.  
  52.                 return Observable.throw(error);  
  53.   
  54.             });  
  55.   
  56.     }  
  57.   
  58.     /** 
  59.      * Creates a new user. 
  60.      * 
  61.      * @param model User's data 
  62.      * @return An IdentityResult 
  63.      */  
  64.     public Create(model: any): Observable<any> {  
  65.   
  66.         let body: string = JSON.stringify(model);  
  67.   
  68.         return this.http.post("/api/identity/Create", body, this.options)  
  69.             .map((res: Response) => {  
  70.   
  71.                 return res.json();  
  72.   
  73.             })  
  74.             .catch((error: any) => {  
  75.   
  76.                 // Error on post request.  
  77.                 return Observable.throw(error);  
  78.   
  79.             });  
  80.   
  81.     }  
  82.   
  83.     /** 
  84.      * Deletes a user through AuthHttp. 
  85.      *  
  86.      * @param username Username of the user 
  87.      * @return An IdentityResult 
  88.      */  
  89.     public Delete(username: string): Observable<any> {  
  90.   
  91.         let body: string = JSON.stringify(username);  
  92.   
  93.         // Sends an authenticated request.  
  94.         return this.authHttp.post("/api/identity/Delete", body, this.options)  
  95.             .map((res: Response) => {  
  96.   
  97.                 return res.json();  
  98.   
  99.             })  
  100.             .catch((error: any) => {  
  101.   
  102.                 // Error on post request.  
  103.                 return Observable.throw(error);  
  104.   
  105.             });  
  106.   
  107.     }  
  108.   
  109.     // Add other methods.  
  110.   
  111. }  
Here, we have functions which allow us to Create, Delete, and Get All users of the current application. Please note that for this purpose, we are using the http get/post methods pointing to the URI (Unique Resource Identifier) of API.
 
 
Now, let's take a look at the Authentication Service.
  1. import { Injectable } from '@angular/core';  
  2. import { Http, Headers, RequestOptions, Response } from '@angular/http';  
  3. import { Observable } from 'rxjs/Observable';  
  4. import 'rxjs/add/operator/map';  
  5. import 'rxjs/add/operator/catch';  
  6. import 'rxjs/add/observable/throw';  
  7.   
  8. import { JwtHelper, tokenNotExpired } from 'angular2-jwt';  
  9.   
  10. import { Config } from '../config';  
  11.   
  12. /** 
  13.  * ROPC Authentication service. 
  14.  */  
  15. @Injectable() export class AuthenticationService {  
  16.   
  17.     /** 
  18.      * Stores the URL so we can redirect after signing in. 
  19.      */  
  20.     public redirectUrl: string;  
  21.   
  22.     /** 
  23.      * User's data. 
  24.      */  
  25.     private user: any = {};  
  26.   
  27.     private headers: Headers;  
  28.     private options: RequestOptions;  
  29.   
  30.     constructor(private http: Http) {  
  31.   
  32.         // On bootstrap or refresh, tries to get the user's data.  
  33.         this.decodeToken();  
  34.   
  35.         // Creates header for post requests.  
  36.         this.headers = new Headers({ 'Content-Type''application/x-www-form-urlencoded' });  
  37.         this.options = new RequestOptions({ headers: this.headers });  
  38.   
  39.     }  
  40.   
  41.     /** 
  42.      * Tries to sign in the user. 
  43.      * 
  44.      * @param username 
  45.      * @param password 
  46.      * @return The user's data 
  47.      */  
  48.     public signin(username: string, password: string): Observable<any> {  
  49.   
  50.         // Token endpoint & params.  
  51.         let tokenEndpoint: string = Config.TOKEN_ENDPOINT;  
  52.   
  53.         let params: any = {  
  54.             client_id: Config.CLIENT_ID,  
  55.             grant_type: Config.GRANT_TYPE,  
  56.             username: username,  
  57.             password: password,  
  58.             scope: Config.SCOPE  
  59.         };  
  60.   
  61.         // Encodes the parameters.  
  62.         let body: string = this.encodeParams(params);  
  63.   
  64.         return this.http.post(tokenEndpoint, body, this.options)  
  65.             .map((res: Response) => {  
  66.   
  67.                 let body: any = res.json();  
  68.   
  69.                 // Sign in successful if there's an access token in the response.  
  70.                 if (typeof body.access_token !== 'undefined') {  
  71.   
  72.                     // Stores access token & refresh token.  
  73.                     this.store(body);  
  74.   
  75.                 }  
  76.   
  77.             }).catch((error: any) => {  
  78.   
  79.                 // Error on post request.  
  80.                 return Observable.throw(error);  
  81.   
  82.             });  
  83.   
  84.     }  
  85.   
  86.     /** 
  87.      * Tries to get a new token using refresh token. 
  88.      */  
  89.     public getNewToken(): void {  
  90.   
  91.         let refreshToken: string = localStorage.getItem('refresh_token');  
  92.   
  93.         if (refreshToken != null) {  
  94.   
  95.             // Token endpoint & params.  
  96.             let tokenEndpoint: string = Config.TOKEN_ENDPOINT;  
  97.   
  98.             let params: any = {  
  99.                 client_id: Config.CLIENT_ID,  
  100.                 grant_type: "refresh_token",  
  101.                 refresh_token: refreshToken  
  102.             };  
  103.   
  104.             // Encodes the parameters.  
  105.             let body: string = this.encodeParams(params);  
  106.   
  107.             this.http.post(tokenEndpoint, body, this.options)  
  108.                 .subscribe(  
  109.                 (res: Response) => {  
  110.   
  111.                     let body: any = res.json();  
  112.   
  113.                     // Successful if there's an access token in the response.  
  114.                     if (typeof body.access_token !== 'undefined') {  
  115.   
  116.                         // Stores access token & refresh token.  
  117.                         this.store(body);  
  118.   
  119.                     }  
  120.   
  121.                 });  
  122.   
  123.         }  
  124.   
  125.     }  
  126.   
  127.     /** 
  128.      * Revokes token. 
  129.      */  
  130.     public revokeToken(): void {  
  131.   
  132.         let token: string = localStorage.getItem('id_token');  
  133.   
  134.         if (token != null) {  
  135.   
  136.             // Revocation endpoint & params.  
  137.             let revocationEndpoint: string = Config.REVOCATION_ENDPOINT;  
  138.   
  139.             let params: any = {  
  140.                 client_id: Config.CLIENT_ID,  
  141.                 token_type_hint: "access_token",  
  142.                 token: token  
  143.             };  
  144.   
  145.             // Encodes the parameters.  
  146.             let body: string = this.encodeParams(params);  
  147.   
  148.             this.http.post(revocationEndpoint, body, this.options)  
  149.                 .subscribe(  
  150.                 () => {  
  151.   
  152.                     localStorage.removeItem('id_token');  
  153.   
  154.                 });  
  155.   
  156.         }  
  157.   
  158.     }  
  159.   
  160.     /** 
  161.      * Revokes refresh token. 
  162.      */  
  163.     public revokeRefreshToken(): void {  
  164.   
  165.         let refreshToken: string = localStorage.getItem('refresh_token');  
  166.   
  167.         if (refreshToken != null) {  
  168.   
  169.             // Revocation endpoint & params.  
  170.             let revocationEndpoint: string = Config.REVOCATION_ENDPOINT;  
  171.   
  172.             let params: any = {  
  173.                 client_id: Config.CLIENT_ID,  
  174.                 token_type_hint: "refresh_token",  
  175.                 token: refreshToken  
  176.             };  
  177.   
  178.             // Encodes the parameters.  
  179.             let body: string = this.encodeParams(params);  
  180.   
  181.             this.http.post(revocationEndpoint, body, this.options)  
  182.                 .subscribe(  
  183.                 () => {  
  184.   
  185.                     localStorage.removeItem('refresh_token');  
  186.   
  187.                 });  
  188.   
  189.         }  
  190.   
  191.     }  
  192.   
  193.     /** 
  194.      * Removes user and revokes tokens. 
  195.      */  
  196.     public signout(): void {  
  197.   
  198.         this.redirectUrl = null;  
  199.   
  200.         this.user = {};  
  201.   
  202.         // Revokes token.  
  203.         this.revokeToken();  
  204.   
  205.         // Revokes refresh token.  
  206.         this.revokeRefreshToken();  
  207.   
  208.     }  
  209.   
  210.     /** 
  211.      * Gets user's data. 
  212.      * 
  213.      * @return The user's data 
  214.      */  
  215.     public getUser(): any {  
  216.   
  217.         return this.user;  
  218.   
  219.     }  
  220.   
  221.     /** 
  222.      * Decodes token through JwtHelper. 
  223.      */  
  224.     private decodeToken(): void {  
  225.   
  226.         if (tokenNotExpired()) {  
  227.   
  228.             let token: string = localStorage.getItem('id_token');  
  229.   
  230.             let jwtHelper: JwtHelper = new JwtHelper();  
  231.             this.user = jwtHelper.decodeToken(token);  
  232.   
  233.         }  
  234.   
  235.     }  
  236.   
  237.     /** 
  238.      * // Encodes the parameters. 
  239.      * 
  240.      * @param params The parameters to be encoded 
  241.      * @return The encoded parameters 
  242.      */  
  243.     private encodeParams(params: any): string {  
  244.   
  245.         let body: string = "";  
  246.         for (let key in params) {  
  247.             if (body.length) {  
  248.                 body += "&";  
  249.             }  
  250.             body += key + "=";  
  251.             body += encodeURIComponent(params[key]);  
  252.         }  
  253.   
  254.         return body;  
  255.     }  
  256.   
  257.     /** 
  258.      * Stores access token & refresh token. 
  259.      * 
  260.      * @param body The response of the request to the token endpoint 
  261.      */  
  262.     private store(body: any): void {  
  263.   
  264.         // Stores access token in local storage to keep user signed in.  
  265.         localStorage.setItem('id_token', body.access_token);  
  266.         // Stores refresh token in local storage.  
  267.         localStorage.setItem('refresh_token', body.refresh_token);  
  268.   
  269.         // Decodes the token.  
  270.         this.decodeToken();  
  271.   
  272.     }  
  273.   
  274. }  
Here, we can note the usage of JWT (JavaScript Web Token); this service is responsible for performing the login/logout functionalities, and also for managing the life cycle of the logged-in user object.

The method getUser() retrieves user which is currently logged in, in order to be able to access information of that user.

Now, let’s take a look at the AuthGuard.
  1. import { Injectable } from '@angular/core';  
  2. import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';  
  3.   
  4. import { tokenNotExpired } from 'angular2-jwt';  
  5.   
  6. import { AuthenticationService } from './authentication.service';  
  7.   
  8. /** 
  9.  * Decides if a route can be activated. 
  10.  */  
  11. @Injectable() export class AuthGuard implements CanActivate {  
  12.   
  13.     constructor(public authenticationService: AuthenticationService, private router: Router) { }  
  14.   
  15.     public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {  
  16.   
  17.         if (tokenNotExpired()) {  
  18.             // Signed in.  
  19.             return true;  
  20.         }  
  21.         // Stores the attempted URL for redirecting.  
  22.         let url: string = state.url;  
  23.         this.authenticationService.redirectUrl = url;  
  24.         // Not signed in so redirects to signin page.  
  25.         this.router.navigate(['/signin']);  
  26.         return false;  
  27.     }  
  28.   
  29. }  
This guard performs validation, to see whether the user is logged in or not. Guards are normally used and associated with routes of the Angular2 application, to check whether user can access the route or not, and in our case the condition to pass the guard is that the user should be logged in.

Also, this guard redirects a user to the login component if the user is currently not logged in.

Here is the example of how we can apply the guard to some specific routes.
  1. Routes = [  
  2.     { path: '', redirectTo: 'home', pathMatch: 'full' },  
  3.     { path: 'home', component: HomeComponent },  
  4.     { path: 'resources', component: ResourcesComponent, canActivate: [AuthGuard] },  
  5.     { path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuard] },  
  6.     { path: 'signin', component: SigninComponent },  
  7.     { path: 'signup', component: SignupComponent }  
  8. ];  
  9. So the main idea here is to separate public routes from ones which requires user to be logged in.   
  10.   
  11. Now let’s take a look at the asp.net web api example:  
  12. using System;  
  13. using System.Collections.Generic;  
  14. using System.Linq;  
  15. using System.Threading.Tasks;  
  16. using Microsoft.AspNetCore.Http;  
  17. using Microsoft.AspNetCore.Mvc;  
  18. using Microsoft.AspNetCore.Authorization;  
  19. using ******.DataCore.Models;  
  20. using Microsoft.AspNetCore.Identity;  
  21. using ******.ApiCore.Services;  
  22. using ******.DataCore.Access;  
  23. using Microsoft.Extensions.Logging;  
  24. using System.Security.Claims;  
  25. using Microsoft.AspNetCore.Identity.EntityFrameworkCore;  
  26. using IdentityModel;  
  27. using ******.ApiCore.Models.AccountViewModels;  
  28. using IdentityServer4.Extensions;  
  29. using Microsoft.EntityFrameworkCore;  
  30.   
  31. namespace ******.ApiCore.Controllers  
  32. {  
  33.     /// <summary>  
  34.     /// Identity Web API controller.  
  35.     /// </summary>  
  36.     [Route("api/[controller]")]  
  37.     //[Authorize(Policy = "Manage Accounts")] // Authorization policy for this API.  
  38.     public class IdentityController : ControllerBase  
  39.     {  
  40.         private readonly UserManager<ApplicationUser> _userManager;  
  41.         private readonly SignInManager<ApplicationUser> _signInManager;  
  42.         private readonly IEmailSender _emailSender;  
  43.         private readonly ISmsSender _smsSender;  
  44.         private readonly ILogger _logger;  
  45.         private readonly ApplicationDbContext _context;  
  46.   
  47.         public IdentityController(  
  48.             UserManager<ApplicationUser> userManager,  
  49.             SignInManager<ApplicationUser> signInManager,  
  50.             IEmailSender emailSender,  
  51.             ISmsSender smsSender,  
  52.             ILoggerFactory loggerFactory,  
  53.             ApplicationDbContext context)  
  54.         {  
  55.             _userManager = userManager;  
  56.             _signInManager = signInManager;  
  57.             _emailSender = emailSender;  
  58.             _smsSender = smsSender;  
  59.             _logger = loggerFactory.CreateLogger<IdentityController>();  
  60.             _context = context;  
  61.         }  
  62.   
  63.         [HttpGet]  
  64.         [AllowAnonymous]  
  65.         public void Get()  
  66.         {  
  67.             var sports = _context.Sports.Include(x => x.Associations);  
  68.   
  69.             var sport = _context.Associations;  
  70.         }  
  71.   
  72.         /// <summary>  
  73.         /// Gets all the users (user role).  
  74.         /// </summary>  
  75.         /// <returns>Returns all the users</returns>  
  76.         // GET api/identity/getall  
  77.         [HttpGet("GetAll")]  
  78.         public async Task<IActionResult> GetAll()  
  79.         {  
  80.             var claim = new Claim("role""user");  
  81.             var users = await _userManager.GetUsersForClaimAsync(claim);  
  82.   
  83.             return new JsonResult(users);  
  84.         }  
  85.   
  86.         /// <summary>  
  87.         /// Registers a new user.  
  88.         /// </summary>  
  89.         /// <returns>IdentityResult</returns>  
  90.         // POST: api/identity/register  
  91.         [HttpPost]  
  92.         [AllowAnonymous]  
  93.         public async Task<IActionResult> Register([FromBody]RegisterViewModel model)  
  94.         {  
  95.             var user = new ApplicationUser  
  96.             {  
  97.                 UserName = model.Username,  
  98.                 Email = model.Email  
  99.             };  
  100.   
  101.             var claims = new IdentityUserClaim<string>[]   
  102.             {  
  103.                 new IdentityUserClaim<string> { ClaimType = JwtClaimTypes.PreferredUserName, ClaimValue = model.Username },  
  104.                 new IdentityUserClaim<string> { ClaimType = JwtClaimTypes.Email, ClaimValue = model.Email },  
  105.                 new IdentityUserClaim<string> { ClaimType = JwtClaimTypes.Role, ClaimValue = "user" }  
  106.             };  
  107.   
  108.             foreach (var claim in claims)  
  109.             {  
  110.                 user.Claims.Add(claim);  
  111.             }  
  112.   
  113.             var result = await _userManager.CreateAsync(user, model.Password);  
  114.   
  115.             // Option: enable account confirmation and password reset.  
  116.   
  117.             return new JsonResult(result);  
  118.         }  
  119.   
  120.         /// <summary>  
  121.         /// Deletes a user.  
  122.         /// </summary>  
  123.         /// <returns>IdentityResult</returns>  
  124.         // POST: api/identity/delete  
  125.         [HttpPost("delete")]  
  126.         public async Task<IActionResult> Delete([FromBody]string username)  
  127.         {  
  128.             var user = await _userManager.FindByNameAsync(username);  
  129.   
  130.             var result = await _userManager.DeleteAsync(user);  
  131.   
  132.             return new JsonResult(result);  
  133.         }  
  134.   
  135.         // Add other methods.  
  136.   
  137.     }  
  138. }  
Here, you have the mapping of paths and methods from identityService. I’m using the standard authentication from .NET, based on .NET identity.

The very same could be done for the sign in and sign out. We just need to generate and store JWT in our front-end Angular 2 application, and then use the stored token to make sure if the user is logged in or not.

You must try this guide. If you face any issue while implementing authentication with Angular 2 apps and ASP.NET Web API, ask me in the comments. Your feedback is valuable, so share your thoughts regarding this post.