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!