Tuesday, July 7, 2020

Easily Enable Azure AD Authentication In Angular And ASP.NET Core Web API App

 Features
  •  Azure Active Directory integration
  • Angular front end working with web api 
   *** 
 Design and code
  •  very simple and working solution
 ***** 


Introduction


Azure Active Directory is the Identity and Access Management (IAM) solution offered by Microsoft.
Azure AD can authenticate accounts from different sources, which are as follows:
  • Azure Subscription
  • Office 365 Subscription
  • Microsoft Intune and Dynamics Subscription
  • Third Party cloud-based SaaS applications (which supports Azure AD authentication)
  • On Premise Active Directory accounts
You can refer to this Microsoft document to get more details about the Azure AD authentication.
We need two app registrations in Azure portal for AD authentication. One for Web API application and one for Angular application.

Create App registrations in Azure portal


Login to Azure portal -> click Azure Active Directory blade
 
Choose App registration blade
 
Click + New registration
 
Give a valid name and redirect URI here. We can give the redirect URI in angular code as well.
 
 
 
Click “Register” button to create the app.
 
 
We can see the app registration details like client id, tenant id etc. We must use these details later in our Angular application.
 
Click Authentication tab in the left side and select Access Token and Id tokens and click Save button.
 
 
We will use these tokens for our Authentication and Authorization purpose later. We can click App registration blade again and create a new app registration for Web API.
 
We have given app name only. No need to give redirect URI here because this is for API.
 
Click “Register” button to create app.
 
After the app registration will be populated, please click “Expose an API” blade.
 
Click Add Scope button
 
 
Click Save button.
 
One new window will be appeared and we can enter scope name, consent name and consent description in this window.
 
 
We will use this consent scope later in our Angular application.
 
We can link our previously created client application to this API app registration.
 
Click + Add a client application button and give correct client id from previously created app registration. Don’t forget to select consent scope along with client id.
 
 
We have successfully created app registration for both UI and API. Now we can create ASP.NET Core web API and Angular 8 application and enable Azure AD authentication.

Create ASP.NET Core Web API in Visual Studio 2019


We can create ASP.NET Core Web API application using default API template in Visual Studio.
 
We must install “Microsoft.AspNetCore.Authentication.AzureAD.UI” library using NuGet. This is used for AD authentication.
 
We have already created two app registrations in Azure active directory. We can use the client id and tenant id for API here in appsettings as given below.
 
appsettings.json
  1. {  
  2.   "Logging": {  
  3.     "LogLevel": {  
  4.       "Default""Information",  
  5.       "Microsoft""Warning",  
  6.       "Microsoft.Hosting.Lifetime""Information"  
  7.     }  
  8.   },  
  9.   "AllowedHosts""*",  
  10.   "AzureActiveDirectory": {  
  11.     "Instance""https://login.microsoftonline.com/",  
  12.     "Domain""<your domain>.onmicrosoft.com",  
  13.     "TenantId""adbbbd82-76e5-4952-8531-3cc59f3c1fdd",  
  14.     "ClientId""api://e283d8fb-22ad-4e2c-9541-14f6f118a08f"  
  15.   }  
  16. }  
We can register authentication service inside the ConfigureServices method in Startup class. Also add CORS service as well.
 
Staturp.cs
  1. using Microsoft.AspNetCore.Authentication;  
  2. using Microsoft.AspNetCore.Authentication.AzureAD.UI;  
  3. using Microsoft.AspNetCore.Builder;  
  4. using Microsoft.AspNetCore.Hosting;  
  5. using Microsoft.Extensions.Configuration;  
  6. using Microsoft.Extensions.DependencyInjection;  
  7. using Microsoft.Extensions.Hosting;  
  8. using System;  
  9.   
  10. namespace AzureADAPI  
  11. {  
  12.     public class Startup  
  13.     {  
  14.         public Startup(IConfiguration configuration)  
  15.         {  
  16.             Configuration = configuration;  
  17.         }  
  18.   
  19.         public IConfiguration Configuration { get; }  
  20.   
  21.         // This method gets called by the runtime. Use this method to add services to the container.  
  22.         public void ConfigureServices(IServiceCollection services)  
  23.         {  
  24.             services.AddControllers();  
  25.   
  26.             services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme).AddAzureADBearer(options => Configuration.Bind("AzureActiveDirectory", options));  
  27.   
  28.             string corsDomains = "http://localhost:4200";  
  29.             string[] domains = corsDomains.Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);  
  30.   
  31.             services.AddCors(o => o.AddPolicy("AppCORSPolicy", builder =>  
  32.             {  
  33.                 builder.AllowAnyOrigin()  
  34.                        .AllowAnyMethod()  
  35.                        .AllowAnyHeader()  
  36.                        .AllowCredentials()  
  37.                        .WithOrigins(domains);  
  38.             }));  
  39.   
  40.         }  
  41.   
  42.         // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.  
  43.         public void Configure(IApplicationBuilder app, IWebHostEnvironment env)  
  44.         {  
  45.             if (env.IsDevelopment())  
  46.             {  
  47.                 app.UseDeveloperExceptionPage();  
  48.             }  
  49.   
  50.             app.UseCors("AppCORSPolicy");  
  51.   
  52.             app.UseRouting();  
  53.   
  54.             app.UseAuthentication();  
  55.             app.UseAuthorization();  
  56.   
  57.             app.UseEndpoints(endpoints =>  
  58.             {  
  59.                 endpoints.MapControllers();  
  60.             });  
  61.         }  
  62.     }  
  63. }  
Create an Employee class. This will be used in our Employees controller class to return some dummy data to Angular application later.
 
Employee.cs
  1. namespace AzureADAPI  
  2. {  
  3.     public class Employee  
  4.     {  
  5.         public int Id { getset; }  
  6.         public string Name { getset; }  
  7.         public string Company { getset; }  
  8.         public string City { getset; }  
  9.     }  
  10. }  
Create Employee controller with single Get method. This method will be called from Angular application to test AD authentication.
 
EmployeeController.cs
  1. using Microsoft.AspNetCore.Authorization;  
  2. using Microsoft.AspNetCore.Mvc;  
  3. using System.Collections.Generic;  
  4.   
  5. // For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860  
  6.   
  7. namespace AzureADAPI.Controllers  
  8. {  
  9.     [Authorize]  
  10.     [Route("api/[controller]")]  
  11.     public class EmployeesController : Controller  
  12.     {  
  13.         [HttpGet]  
  14.         public IEnumerable<Employee> Get()  
  15.         {  
  16.             List<Employee> employees = new List<Employee>  
  17.             {  
  18.                 new Employee { Id = 1, Name = "Sarathlal Saseendran", Company = "Orion Business Innovations", City = "Kochi" },  
  19.                 new Employee { Id = 2, Name = "Anil Soman", Company = "Cognizant", City = "Bangalare" }  
  20.             };  
  21.             return employees;  
  22.         }  
  23.     }  
  24. }  
Please note, we have decorated above controller with [Authorize] decorator.
 
We have completed the API application enabled with AD authentication. We can create Angular application from scratch and add all the components and services.
 

Create Angular 8 application using Angular CLI


I am still using the stable version of Angular 8 LTS (8.3.26)
 
As we discussed earlier, we are using “@azure/msal-angular” library for AD authentication. Unfortunately, the latest version is still in preview mode. Hence, I am using the stable version 0.1.4 in our application.
 
Please use below command to install this stable version.
 
npm i @azure/msal-angular@0.1.4
 
We need RxJs compatible version also in our application.
 
Please install that also.
 
npm i rxjs-compat
 
We can add AD related client id, tenant id, consent scope details inside the environment variable.
 
environment.ts
  1. export const environment = {  
  2.   production: false,  
  3.   baseUrl:'http://localhost:58980/',  
  4.   scopeUri: ['api://e283d8fb-22ad-4e2c-9541-14f6f118a08f/sarath'],  
  5.   tenantId: 'adbbbd82-76e5-4952-8531-3cc59f3c1fdd',  
  6.   uiClienId: '28a65047-6d13-4566-aba6-bd6d6dcd170b',  
  7.   redirectUrl: 'http://localhost:4200'  
  8. };  
We can create a services folder and create a MsalUserService class.
 
msaluser.service.ts
  1. import { Injectable } from '@angular/core';  
  2. import * as Msal from 'msal';  
  3. import { environment } from 'src/environments/environment';  
  4. import { Observable } from 'rxjs';  
  5.   
  6. @Injectable()  
  7. export class MsalUserService {  
  8.     private accessToken: any;  
  9.     public clientApplication: Msal.UserAgentApplication = null;  
  10.     constructor() {  
  11.         this.clientApplication = new Msal.UserAgentApplication(  
  12.             environment.uiClienId,   
  13.             'https://login.microsoftonline.com/' + environment.tenantId,  
  14.             this.authCallback,  
  15.             {  
  16.                 storeAuthStateInCookie: true,  
  17.                 //cacheLocation: 'localStorage' ,  
  18.             });  
  19.     }  
  20.   
  21.     public GetAccessToken(): Observable<any> {  
  22.         if (sessionStorage.getItem('msal.idtoken') !== undefined && sessionStorage.getItem('msal.idtoken') != null) {  
  23.             this.accessToken = sessionStorage.getItem('msal.idtoken');  
  24.         }  
  25.         return this.accessToken;  
  26.     }  
  27.   
  28.     public authCallback(errorDesc, token, error, tokenType) {  
  29.         if (token) {  
  30.   
  31.         } else {  
  32.             console.log(error + ':' + errorDesc);  
  33.         }  
  34.     }  
  35.   
  36.     public getCurrentUserInfo() {  
  37.         const user = this.clientApplication.getUser();  
  38.         alert(user.name);  
  39.     }  
  40.   
  41.     public logout() {  
  42.         this.clientApplication.logout();  
  43.       }  
  44. }  
We have used this service for getting access token (for authentication), logout function and also for getting logged in user name.
 
We can create an employee class
 
Employee.ts
  1. export class Employee {  
  2.     id: number;  
  3.     name: string;  
  4.     company: string;  
  5.     city: string;  
  6. }  
We can create a DataService for getting employee data from Web API application.
 
data.service.ts
  1. import { Injectable } from '@angular/core';  
  2. import { HttpClient, HttpHeaders } from '@angular/common/http';  
  3. import { Observable } from 'rxjs';  
  4. import { environment } from 'src/environments/environment';  
  5. import { MsalUserService } from './msaluser.service';  
  6. import { Employee } from './employee';  
  7.   
  8. @Injectable({  
  9.     providedIn: 'root'  
  10. })  
  11. export class DataService {  
  12.     private url = environment.baseUrl + 'api/employees';  
  13.   
  14.     httpOptions = {  
  15.         headers: new HttpHeaders({  
  16.             'Content-Type''application/json'  
  17.         })  
  18.     };  
  19.   
  20.     constructor(private http: HttpClient, private msalService: MsalUserService  
  21.     ) { }  
  22.   
  23.     getEmployees(): Observable<Employee[]> {  
  24.          
  25.         this.httpOptions = {  
  26.             headers: new HttpHeaders({  
  27.                 'Content-Type''application/json',  
  28.                 'Authorization''Bearer ' + this.msalService.GetAccessToken()  
  29.             })  
  30.   
  31.         };  
  32.   
  33.         return this.http.get(this.url, this.httpOptions)  
  34.             .pipe((response: any) => {  
  35.                 return response;  
  36.             });  
  37.     }  
  38.   
  39.     getCurrentUserInfo(){  
  40.         this.msalService.getCurrentUserInfo();  
  41.     }  
  42.   
  43.     logout(){  
  44.         this.msalService.logout();  
  45.     }  
  46. }    
For every request, we are calling access token from MsalUserService and added to request header.
 
We can add default MsalGuard inside the AppRoutingModule class.
 
app-routing.module.ts
  1. import { NgModule } from '@angular/core';  
  2. import { Routes, RouterModule } from '@angular/router';  
  3. import { AppComponent } from './app.component';  
  4. import { MsalGuard } from '@azure/msal-angular';  
  5.   
  6.   
  7. const routes: Routes = [  
  8.   {  
  9.     path: '',  
  10.     component: AppComponent,  
  11.     canActivate: [MsalGuard]  
  12.   }  
  13. ];  
  14.   
  15. @NgModule({  
  16.   imports: [RouterModule.forRoot(routes)],  
  17.   exports: [RouterModule]  
  18. })  
  19. export class AppRoutingModule { }  
Whenever we route to App component, MsalGuard will protect the component with AD authentication.
 
We can modify the AppModule class with below code.
 
app.module.ts
  1. import { BrowserModule } from '@angular/platform-browser';  
  2. import { NgModule } from '@angular/core';  
  3.   
  4. import { AppRoutingModule } from './app-routing.module';  
  5. import { AppComponent } from './app.component';  
  6. import { environment } from 'src/environments/environment';  
  7. import { MsalModule, MsalInterceptor } from '@azure/msal-angular';  
  8. import { HttpClientModule, HttpClient, HTTP_INTERCEPTORS } from '@angular/common/http';  
  9. import { MsalUserService } from './services/msaluser.service';  
  10.   
  11. export const protectedResourceMap: any =  
  12.   [  
  13.     [environment.baseUrl, environment.scopeUri  
  14.     ]  
  15.   ];  
  16.   
  17. @NgModule({  
  18.   declarations: [  
  19.     AppComponent  
  20.   ],  
  21.   imports: [  
  22.     MsalModule.forRoot({  
  23.       clientID: environment.uiClienId,  
  24.       authority: 'https://login.microsoftonline.com/' + environment.tenantId,  
  25.       //cacheLocation: 'localStorage',  
  26.       protectedResourceMap: protectedResourceMap,  
  27.       redirectUri: environment.redirectUrl  
  28.     }),  
  29.     BrowserModule,  
  30.     AppRoutingModule,  
  31.     HttpClientModule  
  32.   ],  
  33.   providers: [  
  34.     HttpClient,  
  35.     MsalUserService,  
  36.     {  
  37.       provide: HTTP_INTERCEPTORS, useClass: MsalInterceptor, multi: true  
  38.     }  
  39.   ],  
  40.   bootstrap: [AppComponent]  
  41. })  
  42. export class AppModule { }  
We have registered MsalModule and MsalInterceptor inside the AppModule.
 
We can modify the default App component with below code.
 
app.component.ts
  1. import { Component } from '@angular/core';  
  2. import { Employee } from './services/employee';  
  3. import { DataService } from './services/data.service';  
  4.   
  5. @Component({  
  6.   selector: 'app-root',  
  7.   templateUrl: './app.component.html',  
  8.   styleUrls: ['./app.component.css']  
  9. })  
  10. export class AppComponent {  
  11.   title = 'AzureMSALAngular';  
  12.   
  13.   employees: Employee[];  
  14.   errorMessage: any;  
  15.   
  16.   constructor(private dataService: DataService) { }  
  17.   
  18.   ngOnInit(): void {  
  19.     this.dataService.getEmployees().subscribe(  
  20.       values => {  
  21.         this.employees = values;  
  22.       },  
  23.       error => this.errorMessage = <any>error  
  24.     );  
  25.   }  
  26.   
  27.   getUser(){  
  28.     this.dataService.getCurrentUserInfo();  
  29.   }  
  30.   
  31.   logout(){  
  32.     this.dataService.logout();  
  33.   }  
  34. }  
Also modify the template file.
 
app.component.html
  1. <h3>Azure AD Authentication with Azure Angular MSAL library</h3>  
  2.   
  3. <hr>  
  4.   
  5. <table>    
  6.   <thead>    
  7.     <tr>    
  8.       <th>Id</th>    
  9.       <th>Name</th>    
  10.       <th>Company</th>    
  11.       <th>City</th>    
  12.     </tr>    
  13.   </thead>    
  14.   <tbody>    
  15.     <tr *ngFor="let employee of employees">    
  16.       <td>{{ employee.id }}</td>    
  17.       <td>{{ employee.name }}</td>    
  18.       <td>{{ employee.company }}</td>    
  19.       <td>{{ employee.city }}</td>    
  20.     </tr>    
  21.   </tbody>    
  22. </table>    
  23.   
  24. <hr>  
  25.   
  26. <button (click)="getUser()">User Name</button>  
  27. <button (click)="logout()">Logout</button>  
We have completed the coding part of Angular application as well.
 
We can run both Web API and Angular application.
 
Application will immediately ask your AD credentials.
 
 
After the successful login, it will ask your consent to access the AD app registration. Once you approved the consent, it will not ask again.
 
We can see the dummy Employee data has been taken from Web API successfully.
 
If you click the User Name button, you can see the logged in user name.
 
 
 
If you click the Logout button, application will be successfully signed out.

Conclusion


In this post, we have seen how to create an Azure AD enabled ASP.NET Core Web API application and Angular 8 application and communicate with each other. We have used "@azure/msal-angular" library to enable Azure AD in Angular application. This library is a wrapper for base library “msal”. Latest version of this library is still in preview. I have used the stable version. We can create AD enabled application using “msal” library as well. We can see those details in another article.

No comments:

Post a Comment

Free hosting web sites and features -2024

  Interesting  summary about hosting and their offers. I still host my web site https://talash.azurewebsites.net with zero cost on Azure as ...