Welcome to today’s post.
I will discuss how to use Angular to call a secure Web API written in.NET Core.
In a previous post I showed how to execute a call an ASP.NET Core Web API from an Angular UI. In that post, the call to the Web API method was unsecured. In some application scenarios, this would be sufficient if the results of, or the impact on resources from the running of the API method are not required to be secured from the user that is executing the call.
In this post I will focus on a scenario where we use Angular to submit a request to a .NET Core Web API with a secure token that we have received from an identity API.
There are two different authentication process flows that occur in an Angular application when using an identity service and a Web API service. Both are utilized within the application. Let’s first look at how the process flow of each service works.
Securing the Frontend with an Identity Provider Service
The process flow for calling an Angular application that is protected with a secure token is as follows:
- User opens the Angular application.
- The router attempts to open the home page.
- The authorization guard service, auth.guard is activated and checks if there is a user token.
- If there is a user access token, then the user is taken to the home page.
- If there is no user access token, then the user is redirected to the login screen.
- User logins into the application using their email and password through the login() method of the auth.service service.
- The interceptor, auth.interceptor traps the HTTP POST request and if one exists, will add an access token to the HTTP header. With the identity provider, the user authentication method is accessed with no access permissions, so any headers are ignored.
- From the authentication service, a HTTP POST request is made to the identity provider method authenticate() to authenticate the user credentials.
- If the identity method validates the credentials, then it will return a 200 response and an access token to the authentication service and to the home page.
- If the identity method invalidates the credentials, then it will return a 401 (unauthorized) response to the authentication service and redirect the back to the login screen.
Securing a Web API with an Access Token
A similar process flow for accessing secured Web API is as follows:
- User opens the Angular application.
- The router attempts to open the home page.
- User opens a feature page and accesses via the above authentication process steps 1-10.
- When a HTTP GET or HTTP POST request is made to the Web API, the interceptor, auth.interceptor traps the HTTP request and if one exists, will add an access token to the HTTP header.
- If the API method has no permissions on the header, the request will be successful and return a 200 code.
- Any tokens from the HTTP header are passed to the API method and if the token is validated then the request will be successful and return a 200 header. If the token is invalid, then the response to the client will be an unauthenticated 401 error.
Difference between Authentication of the Frontend Interface and the Backend Web API
The difference between the above two ways in which we execute calls to protect our resources is that in the application front end, we make calls to an identity provider to login and obtain an access token or register a new user. When authentication fails, we are re-directed to the application login page. In the web API, any existing access tokens are passed in through the HTTP request header. If the token is valid, then access to the API method is granted and the call to the method body is granted.
A consolidated authentication flow includes calling web API service methods from the Angular frontend. Within each HTTP API call we include the access token in the HTTP header. Provided the token is valid and not expired, the HTTP call to the API method will be authenticated successfully.
The scenarios are depicted in the architectural diagram below:
In the next section, I will show how the web API service methods are secured.
A Secured Web API Service
Below is the API controller method that is secured with a bearer token:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using BookLoan.Models;
using BookLoan.Data;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using BookLoan.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authentication.JwtBearer;
namespace BookLoan.Controllers
{
public class BookController : Controller
{
ApplicationDbContext _db;
IBookService _bookService;
public BookController(ApplicationDbContext db,
IBookService bookService)
{
_db = db;
_bookService = bookService;
}
[HttpGet("api/[controller]/List")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public async Task<List<BookViewModel>> List()
{
try
{
List<BookViewModel> bvm = new List<BookViewModel>();
var books = await _db.Books.ToListAsync();
return books;
}
catch (Exception ex)
{
throw;
}
}
..
}
In the next section, I will show how to execute a web API method from within the Angular frontend.
Executing a Web API Call from the Angular Frontend
The source for the Angular UI component below shows how we call the web API method through the Angular API service:
import { Component } from '@angular/core'
import { ApiService } from '../services/api.service';
@Component({
selector: 'books',
templateUrl: './books.component.html'
})
export class BooksComponent {
book = {}
books
constructor(private api: ApiService) {}
ngOnInit() {
this.api.getBooks().subscribe(res => {
this.books = res
console.log("read books from API");
})
}
selectBook(book) {
this.api.selectBook(book);
}
}
The API service wraps the HTTP GET request to the Book API:
import { Injectable } from '@angular/core'
import { HttpClient, HttpHeaders } from '@angular/common/http'
import { Subject} from 'rxjs'
@Injectable()
export class ApiService {
private selectedBook = new Subject<any>();
bookSelected = this.selectedBook.asObservable();
constructor(private http: HttpClient) {}
getBooks(){
return this.http.get('http://localhost/BookLoan.Catalog.API/api/Book/List');
}
selectBook(book) {
this.selectedBook.next(book)
}
}
In the next section, I will show how we authenticate through the identity provider from an Angular authentication service.
Authentication through an Identity Provider
The authentication service below wraps a basic authentication to the identity API and determines either a successful or unsuccessful login. It also has a helper method currentUserValue() which returns the user login name and assess token:
import { Injectable } from '@angular/core'
import { HttpClient, HttpHeaders } from '@angular/common/http'
import { Router } from '@angular/router'
import { User } from '../models/User';
import { Observable, BehaviorSubject } from 'rxjs';
import { map, catchError, switchMap } from 'rxjs/operators';
@Injectable()
export class AuthService {
private currUserSubject: BehaviorSubject<User>;
public currUser: Observable<User>;
constructor(private http: HttpClient, private router: Router) {
var sUser: string = localStorage.getItem('currentUser');
var sToken: string = localStorage.getItem('token');
var user: User = {
username: sUser,
token: sToken
};
this.currUserSubject = new BehaviorSubject<User>(user);
this.currUser = this.currUserSubject.asObservable();
}
public get currentUserValue(): User {
return this.currUserSubject.value;
}
login(userName: string, password: string){
var config = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
})
};
this.http.post<any>('http://localhost/BookLoan.Identity.API/api/Users/Authenticate', { "UserName": userName, "Password": password }, config).
subscribe(
res => { this.authenticate(res) },
error => { error => console.error('There was an error!', error) }
);
}
register(credentials){
this.http.post<any>('http://localhost/BookLoan.Identity.API/api/Users/Register', credentials).
subscribe(res =>{
this.authenticate(res)
}
)
}
public get currUserValue(): User {
return this.currUserSubject.value;
}
authenticate(res: User) {
localStorage.setItem('token', res.token)
localStorage.setItem('currentUser', JSON.stringify(res.username));
this.currUserSubject.next(res);
t his.router.navigate(['/'])
}
}
In the next section, I will show how to pass tokens from the client HTTP header to Web API method requests.
Passing Access Tokens from the Client HTTP Header to Web API Method Requests
The source below intercepts any HTTP requests from the client and adds an existing access token to the HTTP header prior to submitting the request to the Web API. With any 401 errors that are returned, the user is redirected to the login screen:
import { Injectable } from '@angular/core'
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { Router } from '@angular/router';
import { Observable, throwError } from 'rxjs';
import { map, catchError, finalize } from 'rxjs/operators';
import { AuthService } from './auth.service';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private authenticationService: AuthService,
private router: Router) { }
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const currentUser = this.authenticationService.currentUserValue;
if (currentUser && currentUser.token) {
request = request.clone({
setHeaders: {
Authorization: `Bearer ${currentUser.token}`
}
});
}
return next.handle(request).pipe(map(event => {
return event;
}), catchError(err => {
if (err.status === 401) {
this.router.navigate(['/login'], { queryParams: { returnUrl: this.router.url } });
}
const error = err.error.message || err.statusText;
return throwError(error);
}),
finalize(() => {})
)}
}
In the final section I will show how to protect areas within the Angular with Authorization Guards.
Protecting Angular Application Areas with an Authorization Guard
The source below is used to protect Angular application routes such as \home from unauthorized access. Inadvertent access is redirected back to the login screen:
import { Injectable } from '@angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { AuthService } from './auth.service';
@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
constructor(
private router: Router,
private authService: AuthService
) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const currUser = this.authService.currentUserValue;
if (currUser.token) {
// logged in so return true
return true;
}
this.router.navigate(['/login'], { queryParams: { returnUrl: state.url } });
return false;
}
}
The code below is the routing module that also defines the sensitive routing paths that are guarded by the auth.guard service (see source above) so that access is restricted to authenticated users:
const routes: Routes = [
{ path: 'home', component: HomeComponent, canActivate: [AuthGuard] },
{ path: 'login', component: LoginComponent },
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{ path: 'book-detail/:id', component: BookDetailComponent, canActivate: [AuthGuard] },
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
To summarize, setting up an Angular application for secure API calls with the following:
- Create an angular service that calls the HTTP services.
- Create an authentication service so that we can submit and receive a security token from an identity API.
- Create an HTTP interceptor so that any API requests have the token included within the HTTP request header. In the absence of an active token, we are redirected back to the UI login form.
- Create an authorisation guard for each Angular route so that when we don’t have an active and valid token, then we are redirected back to the UI login screen.
- Declare our auth guard within the Angular route definition so that the route is protected.
That’s all for today’s post.
I hope you found this post useful and informative.
Andrew Halil is a blogger, author and software developer with expertise of many areas in the information technology industry including full-stack web and native cloud based development, test driven development and Devops.