web API with Azure AD authentication in .Net Core

Azure Active Directory is a powerful cloud-based identity and access management service by Microsoft. This is the third article in this series, in which we are using Azure AD for authenticating the applications. Previously, we requested a signed-in user details and profile picture through Microsoft Graph Api. 

Introduction

In this article we’ll

  • Create an API Library.WebApi in Visual Studio
  • Register it in Azure AD
  • Secure Library.WebApi with Azure AD Authentication (Allow users of certain tenant to access its requests). We’ll authenticate JSON Web Tokens (JWT) bearers
  • Create a React App Library.Web.UI to get list of all books from Library.WebApi
  • Requests from this app would need to provide JWT to get authenticated
  • Enable Cors

Prerequisites

  • Visual Studio (I used 2019 here)
  • Microsoft Azure Account (Free sign on if you don’t have one)
  • My previous post

Library.WebApi

Visual Studio -> Create a new Project

Choose the API template and name it Library.WebApi

Register Library.WebApi with Azure AD

App Portal -> App Registration -> New registration

Note down Application (client) ID and Directory (tenant) ID.

Add Redirect URIs. You can get this from Project properties in Visual Studio

Azure AD Authentication

Our application is registered in Azure AD, now inform our project about it. This information is handy to let our webAPI authenticate requests through JSON Web Tokens or JWT. Simply, only those requests will be served which got a token we recognise.

In appsetting.json add

What are JSON Web Tokens (JWT)?

By definition “JSON Web Token (JWT) is an open standard that defines a compact and self-contained way for securely transmitting information between parties as a JSON object.”

In Authorization sense

“Once the user is logged in, each subsequent request will include the JWT (In request header), allowing the user to access routes, services, and resources that are permitted with that token.”

In our webAPI we want to authenticate a valid token bearer, let’s do this

  • NuGet Package Manager -> Browse
  • Search and install -> Microsoft.AspNetCore.Authentication.AzureAD.UI

In Startup.cs AddAuthentication

  • Default Authentication scheme (in our case it is Azure AD)
  • For JWT we’ll use AddJwtBearer extension method to provide required information which we noted in previous step in appsettings.json

Next, in Configure method add

app.UseAuthentication();

Exactly before app.UseMvc();

All done. Add Authorize attribute to ValuesController to guard it againt unauthorized requests.

Build and run the Library.WebApi project.

If you get 401 (Unauthorized) error in browser. It means authentication is working.

BaseApi Controller

Before creating a controller that will offer a Get method with list of books. We’ll first have a base class for the Api Controllers. To have uniformity and less repetition of code, this is a good technique.

Now add GetUserInfo() method in this class. With this method we can get information about authenticated user from the ControllerBase ready-only property User.

using Microsoft.AspNetCore.Mvc;
using System.Linq;

namespace Library.WebApi.Controllers
{
    [Route("api/[controller]")]
    [Produces("application/json")]
    [ApiController]
    public class BaseApiController : ControllerBase
    {
        protected string GetUserInfo(string type)
        {
            return this.User.Claims.First(i => i.Type == type).Value;
        }
    }
}

Book Resource

Add a folder name Resources and a new class BookResource

Books Controller

  • Add new class BooksController in controllers folder
  • Inherit it with BaseApiController
  • Use Authorize attribute at top
  • Add constructor and ILogger in its parameter for dependency injection
  • Add Get Method that produces BookResource
using Library.WebApi.Resources;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;

namespace Library.WebApi.Controllers
{
    [Authorize]
    public class BooksController : BaseApiController
    {
        private readonly ILogger<BooksController> logger;

        public BooksController(ILogger<BooksController> logger)
        {
            this.logger = logger;
        }

        [HttpGet]
        [ProducesResponseType(typeof(BookResource), 200)]
        public IActionResult Get()
        {
            try
            {
                logger.LogInformation("In BooksController Get");

                //Mock data
                var bookResource = new BookResource()
                {
                    UserFullName = GetUserInfo("name"),
                    UserName = GetUserInfo("preferred_username"),
                    Books = new List<Book>()
                {
                    new Book()
                    {
                        BookId=1,
                        BookName="City of Girls",
                        AuthorName="Elizabeth Gilbert",
                        Category="Novel"
                    },
                    new Book()
                    {
                        BookId=2,
                        BookName="The Silent Patient",
                        AuthorName="Alex Michaelides",
                        Category="Thriller"
                    },
                     new Book()
                    {
                        BookId=3,
                        BookName="Once More We Saw Stars",
                        AuthorName="Jayson Greene",
                        Category="Memoir"
                    }
                }
                };

                return Ok(bookResource);
            }
            catch (Exception ex)
            {
                logger.LogError($"Error in BooksController: {ex.Message}");
                return BadRequest($"{BadRequest().StatusCode} : {ex.Message}");

            }
        }
    }
}

Now we are all set to create the React app to call this web API

Library.Web.UI

Create new Project in same solution

Select the React template

Register Library.Web.UI with Azure AD

Copy this Application (client) ID from Library-UI. In App Registration come back to Library-WebApi

Click Manifest and add this Library-UI Application ID in KnownClientApplication list

Now open my previous post. And follow that to build the React app. You may clone this repository also.

If your React app has Sign-in button and is authenticating your credentials. It means you are ready to expand it.

Add a new export const to authProvider

import { MsalAuthProvider, LoginType } from 'react-aad-msal';

export const authProvider = new MsalAuthProvider(
    {
        auth: {
            authority: "https://login.microsoftonline.com/YOUR-TENANT-ID", //Directory (tenant) ID Overview blade of App Registration
            clientId: "YOUR-APPLICATION-CLIENT-ID" //Application (client) ID
        },
        cache: {
            cacheLocation: 'sessionStorage',
            storeAuthStateInCookie: true,
        },
    },
    {
        scopes: ['https://graph.microsoft.com/.default']
    },

    LoginType.Popup
);

export const authFetch = async url => {
    const token = await authProvider.getIdToken();
    return fetch(url, {
        method: 'GET',
        headers: {
            Authorization: 'Bearer ' + token.idToken.rawIdToken,
            'Content-Type': 'application/json',
        },
    });
};

authFetch will return fetch method with ID Token in Authorization header. (We also discussed difference between ID Token and Access Token in Step-3 of this post.

LibraryService

As we have GraphService to communicate with Microsoft Graph Api, we’ll have LibraryService for Library web API.

Add new JavaScript file in services folder

And add following

import config from '../Config';
import { authFetch } from '../authProvider';

export async function getAllBooks() {

    return await authFetch(config.apiBaseUrl + `/api/books`);

}

And Config.js has follwing export

module.exports =
    {
        apiBaseUrl: 'https://localhost:44320'
    }

FetchBooks component

Add FetchBooks component in app to use LibraryService

import React, { Component } from 'react';
import { getAllBooks } from '../services/LibraryService';

export class FetchBooks extends Component {

    state = {
        isLoading: true,
        apiResponse: ''
    };

    async componentDidMount() {

        await getAllBooks().then(
            (response) => {
                if (response.ok) {
                    response.json()
                        .then((responseData) => {
                            this.setState({
                                isLoading: false,
                                apiResponse: JSON.stringify(responseData, null, 2)
                            })
                        });
                }
                else if (response.status === 401) {
                    throw new Error("User not authorized");
                }
                else {
                    throw new Error("Not working");
                }
            })
            .catch((err) => {

                // Don't forget to handle errors!
                console.error(err);
                this.setState({ isLoading: false, apiResponse: 'Error: ' + err.message });
            });

    }

    render() {
        let contents = this.state.isLoading
            ? <p><em>Loading...</em></p>
            : <pre>{this.state.apiResponse}</pre>;

        return (
            <div className="container user-profile">
                {contents}
            </div>
        );
    }
}

Add FetchBooks in App.js

And link in NavMenu.js

Now select multiple startup projects in solution

Build and run.

After successful sign-in, if we click Fetch Books link, we should get this

This is cors error. Technically, call from react app host is blocked to request web API. We need to tell our API that these calls are legit. This is called enabling CORS.

CORS (Cross-Origin Resource Sharing)

Browser’s security policy does not allow web browser to make AJAX requests to a server in another domain. This is also known as same-origin policy.

CORS is a W3C standard that allows you to get away from the same-origin policy. By enabling CORS we let resources share cross domain for known origins.

In Library.WebApi project

In Startup.cs

In LibraryUIAppOrigins we can have list of all urls we want cors to be enabled for.

Libray-WebApi and Library-UI shake-hand

I call this process shake-hand because in Azure AD App Registration we are introducing these apps to eachother in Azure AD and asking them to virtually shake-hand with eachother. Library-UI will be known to its scopes and it’ll be included in Authorized client application list of Library-WebApi.

First we’ll Add a scope in Library-WebApi which Library-UI can access. Read more about this process here.

Azure AD -> App Registraion -> Library-WebApi -> Expose an API

After this we’ll see Add a client application button enabled. Add Library-UI Application (Client) ID there and tick for authorized scopes

App Registraion -> Library-UI -> API Permissions

In this blade press Add a permission button

(In sample Manifest json of both projects is added. You may compare it with your own Manifest of applications in App Registration for settings comparison.)

Back to Visual Studio -> Build and run

Sample

Clone following repository for the code sample.

Please don’t forget to add Applicatoin IDs for your UI and WebApi projects in authprovider.js and appsettings.json respectively.

https://github.com/AhsanRazaUK/webapi-azure-ad

Summary

So, what we learnt in this article

  • Secure a web API with Azure AD using JWT
  • Create a React app which uses Microsoft Graph to generate Id Token that we’ll use in header to autheticate request to web API
  • What is JWT?
  • How to add cors policy in web API?
  • Let UI and web API have a shake-hand in Azure AD

And as usual, feel free to contact me if this all went well for you? Happy coding!

Published by Ahsan Raza

Agile Cooach, Scrum master, Blogger, Programmer, Optimist

Leave a comment

Design a site like this with WordPress.com
Get started