Using Azure AD with ASP.NET Core

Azure Active Directory is cloud-based directory service that allows users to use their personal or corporate accounts to log-in to different applications. Local Active Directory can sync data to its cloud counterpart. Also external users are supported. This blog post shows how to make ASP.NET Core application use Azure AD and how to read data that Azure AD provides about user account.

NB! To use Azure AD valid Microsoft Azure subscription is needed. It also goes for Azure AD services used by Office 365.

Using wizard for Azure AD authentication

Simplest way is adding Azure AD support to application using Visual Studio. Visual Studio 2017 allows to add Azure AD authentication for new applications. Hopefully there will be soon also support for adding Azure AD to existing applications. As this is how-to style post I will go here with new default application.

Steps are simple:

  1. Create new ASP.NET Core application
  2. Choose template
  3. Click on “Change Authentication” button
  4. Select “Work or School Accounts”
  5. Choose Azure AD you want to use
  6. Click “Ok”

ASP.NET Core: Connect to Azure AD

Visual Studio adds automatically new configuration file with all required configuration parametes, updates Startup class of web application and adds Account controller that coordinates authentication processes. If everything went well then we have application that supports Azure AD authentication and we can stop here.

Connecting application to Azure Active Directory manually

If we can’t use nice wizard for some reason then we can enable Azure AD support manually. This section is a short guide to how to do it. Even when Visual Studio wizards work well I suggest you to go through following sections of this blog post too as it gives you better idea how Azure AD support is actually implented.

Add the following NuGet packages to web application:

  • Microsoft.AspNetCore.Authentication.Cookies
  • Microsoft.AspNetCore.Authentication.OpenIdConnect

Add these settings to appsettings.json (this data can be found from Azure Portal).


"Authentication": {
 
"AzureAd"
: {
   
"AADInstance": "https://login.microsoftonline.com/"
,
   
"CallbackPath": "/signin-oidc"
,
   
"ClientId": "your client id"
,
   
"Domain": "your-domain.com"
,
   
"TenantId": "your tenant id"
  }
}

We need couple of changes to Startup class too. To ConfigureServices() method we add call to AddAuthentication() and to Configure() method the call to UseOpenIdConnectAuthentication.


public void ConfigureServices(IServiceCollection services)
{
   
// Add framework services.
    services.AddMvc();

    services.AddAuthentication(
        SharedOptions => SharedOptions.SignInScheme =
                           
CookieAuthenticationDefaults
.AuthenticationScheme
    );
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory
loggerFactory)
{
    loggerFactory.AddConsole(Configuration.GetSection(
"Logging"
));
    loggerFactory.AddDebug();

   
// ...

    app.UseOpenIdConnectAuthentication(
new OpenIdConnectOptions
    {
        ClientId = Configuration[
"Authentication:AzureAd:ClientId"
],
        Authority = Configuration[
"Authentication:AzureAd:AADInstance"] + Configuration["Authentication:AzureAd:TenantId"
],
        CallbackPath = Configuration[
"Authentication:AzureAd:CallbackPath"
]
    });

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name:
"default"
,
            template:
"{controller=Home}/{action=Index}/{id?}");
    });
}

We need additional controller to coordinate authentication operations. The tooling – when it works – adds automatically new Account controller. Here is the code.


public class AccountController : Controller
{
    [
HttpGet
]
   
public IActionResult
SignIn()
    {
       
return
Challenge(
           
new AuthenticationProperties { RedirectUri = "/" }, OpenIdConnectDefaults
.AuthenticationScheme);
    }

    [
HttpGet
]
   
public IActionResult
SignOut()
    {
       
var callbackUrl = Url.Action(nameof(SignedOut), "Account", values: null
, protocol: Request.Scheme);
       
return SignOut(new AuthenticationProperties
{ RedirectUri = callbackUrl },
           
CookieAuthenticationDefaults.AuthenticationScheme, OpenIdConnectDefaults
.AuthenticationScheme);
    }

    [
HttpGet
]
   
public IActionResult
SignedOut()
    {
       
if
(HttpContext.User.Identity.IsAuthenticated)
        {
           
return RedirectToAction(nameof(HomeController.Index), "Home"
);
        }

       
return
View();
    }

    [
HttpGet
]
   
public IActionResult
AccessDenied()
    {
       
return View();
    }
}

It’s easy to see from code that there are two views we need to add. Here is the view for SignedOut action.


@{
    ViewData[
"Title"] = "Sign Out"
;
}

<h2>@ViewData["Title"].</h2
>
<
p class="text-success">You have successfully signed out.</p
>

This is the view for AccessDenied action.


@{
    ViewData[
"Title"] = "Access Denied"
;
}

<header>
    <h1 class="text-danger">Access Denied.</h1>
    <p class="text-danger">You do not have access to this resource.</p
>
</
header
>

Now we are done with coding and it’s time to try out Azure AD authentication.

Trying out Azure AD authentication

When we run our application we are redirected to identity provider login page. In my case it is Microsoft Account login page. Take a look at page title and notice my holy lazyness.

Microsoft Account log-in page

After successful authentication we are returned back to our application. Notice that user name is not e-mail address or GUID or some long sequence of letters and numbers. It has a special format: <authentication provider>#<e-mail address>.

User name with provider

This user name format is good for one thing – it is unique and we can also use it when multiple active directories are available for application users.

What data we get from Azure AD?

User name is not in very user-friendly format and the question is: what we can do to get something better there? Well, it’s a claim based authentication identity and where we should look is claims collection of user. As claims contain also sensitive information I don’t show here screenshot but I show the code that displays claims out.

To display claims that current claim identity has we have to send claims from controller to view. Here is the Index action of Home controller.


public IActionResult Index()
{
   
var claims = ((ClaimsIdentity
)User.Identity).Claims;
   
return View(claims);
}

NB! This code works only if we have identities of type ClaimsIdentity. With other authentication mechanisms we may have other identity types. To support different identitites in same code base we need more common way to detect user attributes.

To show claims on front page we use the following table.


@model IEnumerable<System.Security.Claims.Claim>
@{
    ViewData[
"Title"] = "Home Page"
;
}

<div class="row">
    <div class="col-md-12">
        <table>
            <thead>
                <tr>
                    <th>Claim</th>
                    <th>Value</th>
                </tr>
            </thead>
            <tbody>
                @foreach(var claim in
Model)
                {
                   
<tr>
                        <td>@claim.Type</td>
                        <td>@claim.Value</td>
                    </tr>
                }
           
</tbody>
        </table>
    </div
>
</
div
>

Run application, log in and take a look at table of claims. There is all basic information about user like first name, last name and e-mail address.

Displaying nice user name

User identificator with provider wasn’t the best choice of names we can show to user when he or she is logged in to site. There is _LoginPartial view under shared views folder and this is how this view looks like.


@using System.Security.Principal

@
if
(User.Identity.IsAuthenticated)
{
   
<ul class="nav navbar-nav navbar-right">
        <li class="navbar-text">Hello @User.Identity.Name!</li>
        <li><a asp-area="" asp-controller="Account" asp-action="SignOut">Sign Out</a></li>
    </ul>
}
else
{
   
<ul class="nav navbar-nav navbar-right">
        <li><a asp-area="" asp-controller="Account" asp-action="Signin">Sign in</a></li>
    </ul>
}

We add some additional code here to make this view display full name of user. As we saw from claims table then there is claim called name. This is the claim we will use.


@using System.Security.Principal
@
using
System.Security.Claims
@{ 
   
var claims = ((ClaimsIdentity
)User.Identity).Claims;
   
var name = claims.FirstOrDefault(c => c.Type == "name"
)?.Value;
}

@
if
(User.Identity.IsAuthenticated)
{
   
<ul class="nav navbar-nav navbar-right">
        <li class="navbar-text">Hello @name</li>
        <li><a asp-area="" asp-controller="Account" asp-action="SignOut">Sign Out</a></li>
    </ul>
}
else
{
   
<ul class="nav navbar-nav navbar-right">
        <li><a asp-area="" asp-controller="Account" asp-action="Signin">Sign in</a></li>
    </ul>
}

NB! Instead of writing code to partial view we should use view component and move this mark-up to some view of view component. Here I just wanted to show how to get the full name of current user.

Wrapping up

Adding Azure AD support to ASP.NET Core applications is easy. It can be done using Visual Studio but it also can be done manually. We needed few additional configuration parameters, some lines of code and small change to login view. Although default user name used by ASP.NET Core internally doesn’t look nice we were able to get user e-mail and full name from claims collection we got back from Azure AD.



See also

2 thoughts on “Using Azure AD with ASP.NET Core

Leave a Reply

Your email address will not be published. Required fields are marked *