Background
This article is based on challenges when starting with Angular 6, ADAL and Web API Core 2.1. So its an article from a beginner to beginners, so there will properly be many thinks that should have been done differently. It's a sample of making the most minimal working setup.As stated I am new to Angular, so my first question was how do I work work with Angular. There is only a version 5 as a template in Visual Studio, and I realized fast, that if you want to use Visual Studio as a tool, you have to do some configuration to make Visual Studio and Angular work together. After some conversations with more experienced Angular developers, I decided to split the Web API and the Portal in two separate projects. I work with Web API in Visual Studio, and Angular via the Angular cli (command line interface). In this way I feel I am using the right tool for the right technology and not trying to bend Angular into the Visual Studio world. When working with Angular I use Visual Studio Code as editor and debugger, but its nothing more than an editor. You can use Notepad or what ever you feel like.
Prerequisites
Before starting make sure you have installed:
- NPM
- Angular CLI
- Visual Studio for the Web API
Following frameworks will be used:
- Angular 6
- Asp.Net Core 2.1
- adalangular4
Implementation
Lets get started.
Azure Active Directory Setup
Sign into your Active Directory and create a new app registration.
Notice when you have created your Web API and Angular projects you need to add the reply url's (you properly first know you API url after creating the project. Go back following and add it to Reply url's):
Save following:
TenantId (click Azure Active Directory->Properties)
ClientId
Save following:
TenantId (click Azure Active Directory->Properties)
ClientId
Angular
Navigate to an empty folder where you want your projects to be located, and type following:
ng new AngularPortal --routing
cd AngularPortal
This will create a new Angular Project through the Angular CLI.
Add the ADAL/Angular wrapper:
npm install --save adal-angular4
I am going to use Visual Studio Code so I go (but feel free to use your favorite editor):
AngularPortal Code .
You can try run your application by running:
ng serve --open
Create components needed in the CLI:
ng g class authinterceptor
ng g c secured
ng g c nonsecured
ng g c authcallback
ng g service api
Add following to your files:
authinterceptor.ts
secured.component.ts
Add the ADAL/Angular wrapper:
npm install --save adal-angular4
I am going to use Visual Studio Code so I go (but feel free to use your favorite editor):
AngularPortal Code .
You can try run your application by running:
ng serve --open
Create components needed in the CLI:
ng g class authinterceptor
ng g c secured
ng g c nonsecured
ng g c authcallback
ng g service api
Add following to your files:
authinterceptor.ts
import { Injectable } from '@angular/core';
import { HttpEvent,HttpHandler,HttpRequest,HttpInterceptor } from '@angular/common/http';
import { AdalService } from 'adal-angular4';
import { Observable } from 'rxjs';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private adalService: AdalService) { }
intercept(req: HttpRequest<any>, next:HttpHandler): Observable<HttpEvent<any>>{
const authHeader = this.adalService.userInfo.token;
const authReq = req.clone({headers: req.headers.set('Authorization', `Bearer ${authHeader}`)});
return next.handle(authReq);
}
}
secured.component.ts
import { Component, OnInit } from '@angular/core';
import { ApiService } from '../api.service';
@Component({
selector: 'app-secured',
templateUrl: './secured.component.html',
styleUrls: ['./secured.component.css']
})
export class SecuredComponent implements OnInit {
private contacts: Array<object> = [];
constructor(private apiService: ApiService) { }
ngOnInit() {
}
callApi(){
this.apiService.getValues().subscribe((data: Array<object>) => {
this.contacts = data;
console.log(data);
});
}
}
nonsecured.component.ts
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-nonsecured',
templateUrl: './nonsecured.component.html',
styleUrls: ['./nonsecured.component.css']
})
export class NonsecuredComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
authcallback.component.ts
import { Component, OnInit, NgZone } from '@angular/core';
import { Router } from '@angular/router';
import { AdalService } from 'adal-angular4';
@Component({
selector: 'app-auth-callback',
templateUrl: './authcallback.component.html',
styleUrls: ['./authcallback.component.css']
})
export class AuthCallbackComponent implements OnInit {
constructor(private router: Router, private adalService: AdalService, private _zone: NgZone) { }
ngOnInit() {
var config = {
tenant: 'xxxxxxxx-a8b0-4555-97b3-70001a6a7448',
clientId: 'xxxxxxxx-afc3-4327-b668-9e9c894bc276',
redirectUri: "http://localhost:4200/authcallback/",
logOutUri: "http://localhost:4200",
postLogoutRedirectUri: "http://localhost:4200",
endpoints: {
"https://localhost:44300/": "xxxxxxxx-9947-466c-be42-98c8a44db994"
}};
this.adalService.init(config);
this.adalService.handleWindowCallback();
setTimeout(() => {
this._zone.run(
() => this.router.navigate(['/'])
);
}, 200);
}
}
api.service.ts
import { Injectable } from '@angular/core';
import { HttpClient} from '@angular/common/http';
@Injectable({
providedIn: 'root'
})
export class ApiService {
constructor(private httpClient: HttpClient) {}
getValues(){
return this.httpClient.get("https://localhost:44300/api/values");
}
}
app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { AdalService,AdalGuard } from 'adal-angular4';
import { SecuredComponent } from './secured/secured.component';
import { NonsecuredComponent } from './nonsecured/nonsecured.component';
import { AuthInterceptor } from './auth-interceptor';
import { Nonsecured2Component } from './nonsecured2/nonsecured2.component';
import { AuthCallbackComponent } from './authcallback/authcallback.component';
@NgModule({
declarations: [
AppComponent,
SecuredComponent,
NonsecuredComponent,
Nonsecured2Component,
AuthCallbackComponent
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule
],
providers: [AdalService, AdalGuard,{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }],
bootstrap: [AppComponent]
})
export class AppModule { }
app.component.ts
import { Component,OnInit } from '@angular/core';
import { AdalService } from 'adal-angular4';
import { environment } from '../environments/environment';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
config = {
tenant: 'xxxxxxxx-a8b0-4555-97b3-70001a6a7448',
clientId: 'xxxxxxxx-afc3-4327-b668-9e9c894bc276',
redirectUri: "http://localhost:4200/authcallback/",
logOutUri: "http://localhost:4200",
postLogoutRedirectUri: "http://localhost:4200",
endpoints: {
"https://localhost:44300/": "xxxxxxxx-9947-466c-be42-98c8a44db994"
}};
constructor(private adalService: AdalService)
{
}
ngOnInit() {
}
login()
{
this.adalService.init(this.config);
this.adalService.login();
}
logout()
{
this.adalService.init(this.config);
this.adalService.logOut();
}
}
app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AdalGuard } from 'adal-angular4';
import { SecuredComponent } from './secured/secured.component';
import { NonsecuredComponent } from './nonsecured/nonsecured.component';
import { Nonsecured2Component } from './nonsecured2/nonsecured2.component';
import { AuthCallbackComponent } from './authcallback/authcallback.component';
const routes: Routes = [
{ path: '', component: NonsecuredComponent },
{ path: 'authcallback', component: AuthCallbackComponent },
{ path: 'nonsecured2', component: Nonsecured2Component },
{ path: 'secured', component: SecuredComponent, canActivate: [AdalGuard] },
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
secured.component.html
<p>
secured works!
</p>
<div (click)="callApi()">Call secured API</div>
app.component.html
<!--The content below is only a placeholder and can be replaced.-->
<table>
<tr>
<td routerLink="/">Non Secured</td>
<td div routerLink="/secured">Secured</td>
<td (click)="login()">Login</td>
<td (click)="logout()">Logout</td>
</tr>
<tr>
<td colspan="4">
<router-outlet></router-outlet>
</td>
</tr>
</table>
ASP.NET Core Web API
Create a new ASP.NET Core Web API. I found its most easy to add the authentication while creating the project so the owin middle where is created for you:
Because we in debug are having two different hosts/domains we will need to configure CORS. For this sample I disable CORS. Dont do that in productions. Also we need to tell the API to use bearer authentication. The relevant files are shown below:
Startup.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.AzureAD.UI;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Authentication.JwtBearer;
namespace WebApi21
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme)
.AddAzureADBearer(options => Configuration.Bind("AzureAd", options));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
var authority = Configuration["AzureAd:Instance"] + Configuration["AzureAd:TenantId"];
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = authority;
options.Audience = Configuration["AzureAd:ClientId"]; // web api client id
});
services.AddCors(options =>
{
options.AddPolicy("AllowSpecificOrigin",
builder => builder.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod());
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseCors("AllowSpecificOrigin");
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseMvc();
}
}
}
Values Controller:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace WebApi21.Controllers
{
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
[HttpGet("{id}")]
public ActionResult<string> Get(int id)
{
return "value";
}
// POST api/values
[HttpPost]
public void Post([FromBody] string value)
{
// For more information on protecting this API from Cross Site Request Forgery (CSRF) attacks, see https://go.microsoft.com/fwlink/?LinkID=717803
}
// PUT api/values/5
[HttpPut("{id}")]
public void Put(int id, [FromBody] string value)
{
// For more information on protecting this API from Cross Site Request Forgery (CSRF) attacks, see https://go.microsoft.com/fwlink/?LinkID=717803
}
// DELETE api/values/5
[HttpDelete("{id}")]
public void Delete(int id)
{
// For more information on protecting this API from Cross Site Request Forgery (CSRF) attacks, see https://go.microsoft.com/fwlink/?LinkID=717803
}
}
}
Test
Lets test it. Start your API project by in Visual Studio (F5), and your Angular portal (ng serve):
If you click secured you should not be able so see secured component. Click login, and now you can click Secured. In the secured component click Call secured API and in the console you should see an array from the API.
Feel free to comment article if you have any changes or additions to the description.