Dan Schnau

Implementing Single-User Administrator Authorization with Azure AD in ASP.Net Core 3.1

Until now, this blog was entirely a read-only site. Content is stored in a database, and the app reads from that DB, but there was no code for writing to the DB. In order to update content, I''d manually write SQL queries. I needed to be able to secure some features of the site so I could get away from that practice.

Here were my goals for this:

  • Save no user data in the app.
  • Use my Mirosoft Identity (the same one I use on my Xbox) to log in.
  • Only allow me to Authorize.

I worked off this helpdoc at docs.microsoft.com. It links to a sample project I could use as a reference, which was useful while troubleshooting.

Install Dependencies

Step 1 was to install Nuget package Microsoft.AspNet.Core.Authentication.AzureAD.UI.

Create new Azure AD App registration

To use Azure AD auth, you need to create an App Registration. I did mine point-and-click in the Azure Portal.

More info on how to do this at docs.microsoft.com. By the end of it, I had a TenantId and a ClientId. I also had to make sure my redirect urls were correct - I needed danschnau.com/oidc-login for prod and localhost:8888/oidc-login for local development.

Configure Things in your app

In my ASP.NET Core 3.1 app, I plugged in a few settings into my appsettings.json. These were copied from the sample project.

"AzureAd": {
   "Instance": "https://login.microsoftonline.com/",
   "Domain": "[Enter the domain of your tenant, e.g. contoso.onmicrosoft.com]",
   "TenantId": "TENANT_ID_GOES_HERE",
   "ClientId": "CLIENT_ID_GOES_HERE",
   "CallbackPath": "/signin-oidc"
},

Update Startup.cs

Configure Changes

In Configure, I added app.UseAuthorization() and app.UseAuthentication().

// This method gets called by the runtime. Use this method to configure 
// the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    // snip

    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseRouting();
    app.UseCookiePolicy();

    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints => { /* snip */ });
}

ConfigureServices Changes

In ConfigureServices, I added a few things.

I changed services.ConfigureMvc() to instead to a bit of Authorization configuration:

services.AddMvc(options =>
{
    var policy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .Build();
    options.Filters.Add(new AuthorizeFilter(policy));
});

I also had to call services.AddAuthentication():

services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
    .AddAzureAD(options => Configuration.Bind("AzureAd", options));

Then I had to call services.AddAuthorization, specifying that the only way to be authorized is to have a claim of my email address.

services.AddAuthorization(options =>
{
    options.AddPolicy("adminpolicy", o =>
    {
        o.RequireAssertion(authorizationHandlerContext =>
            authorizationHandlerContext.User
            .HasClaim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", "dsschnau@gmail.com"));
    });
});

It took a bit of trial and error to get that working.

Add [AllowAnonymous] To Public Controllers

This blog is a pretty vanilla MVC app. All the Controllers that service public-facing pages got the [AllowAnonmyous] tag put on their clases.

[AllowAnonymous]
public class HomeController : Controller

Secure A Controller

Then I could force a Controller to lock down access to only me:

    [Authorize("adminpolicy")]
    public class AdminController : Controller

That was it! All done.

Closing Thoughts

This all worked well, taking 2-3 hours to implement this. Having examples, well-written documentation, and helpful error messages, I was never stuck troubleshooting for too long.

There''s more I'd like to do around Authentication/Authorization on Dan Schnau Dot Com in the future - adding the ability to leave comments comes to mind.

If you have feedback, questions or advice - email me at dsschnau@gmail.com.