Security authorization
Angular HTTP REST RxJS SPA Typescript Visual Studio Code Web API

Calling a Secure Web API from an Angular Application

Welcome to today’s post.

I will discuss how to use Angular to call a secure Web API written in.NET Core.

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.

The process for calling a web API that is secured with a secure token is as follows:

  1. User opens the Angular application.
  2. The router attempts to open the home page.
  3. The authorization guard service, auth.guard is activated and checks if there is a user token.
  4. If there is a user access token, then the user is taken to the home page.
  5. If there is no user access token, then the user is redirected to the login screen.
  6. User logins into the application using their email and password through the login() method of the auth.service service.
  7. 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.
  8. From the authentication service, a HTTP POST request is made to the identity provider method authenticate() to authenticate the user credentials.
  9. 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.
  10. 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.

A similar process for accessing secured Web API is as follows:

  1. User opens the Angular application.
  2. The router attempts to open the home page.
  3. User opens a feature page and accesses via the above authentication process steps 1-10.
  4. 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.
  5. If the API method has no permissions on the header, the request will be successful and return a 200 code.
  6. 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.

The scenarios are depicted in the architectural diagram below:

Angular web api

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;
            }
        }
..
}

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)
    }
}

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(['/'])
    }
}

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(() => {})
        )}
}    

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:

  1. Create an angular service that calls the HTTP services.
  2. Create an authentication service so that we can submit and receive a security token from an identity API.
  3. 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.
  4. 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.
  5. 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.

Social media & sharing icons powered by UltimatelySocial