DEV Community

Christos Matskas for The 425 Show

Posted on • Updated on

Securing a .NET Azure Function with Azure AD

Last week I was asked by someone in the community for a sample that shows how to secure Azure Functions with Azure AD. And specifically, a .NET 5 or 6 Azure Function using the Microsoft.Identity.Web library to validate tokens and authorize access.

Watch the video

If you want to watch the 1hr video that shows you how to do it end-to-end, it's available on YouTube

Grab the code

The sample project from the stream is available to clone from GitHub

Prerequisites

Known issues

At the time of writing this, there is a known issue with .NET 6, Azure Functions **v4 and Microsoft.Identity.Web. Therefore, only the magic combination of the libries/NuGet packages mentioned in this blog/video work. The team is aware and working on a fix. What's the magic combo you ask? Your Azure Functions csproj file should have these dependencies (hardcode the version!)

<ItemGroup>
    <PackageReference Include="Microsoft.NET.Sdk.Functions" Version="4.0.1" />
    <PackageReference Include="Microsoft.Azure.Functions.Extensions" Version="1.1.0" />
    <PackageReference Include="Microsoft.Identity.Web" Version="1.5.1" />
  </ItemGroup>
Enter fullscreen mode Exit fullscreen mode

Create the project

The easiest way to get started is to use the .NET command line with the M.I.W extension (check the prereqs ^^^^). Open your favorite terminal and run the following command

mkdir <your directory name>
cd <your directory name>
dotnet new func2 --auth SingleOrg
Enter fullscreen mode Exit fullscreen mode

This will create the files we need and implement the necessary middleware code.

First thing we need to do is open the *.csproj file and update it to look like this:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <AzureFunctionsVersion>v4</AzureFunctionsVersion>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Sdk.Functions" Version="4.0.1" />
    <PackageReference Include="Microsoft.Azure.Functions.Extensions" Version="1.1.0" />
    <PackageReference Include="Microsoft.Identity.Web" Version="1.5.1" />
  </ItemGroup>
  <ItemGroup>
    <None Update="host.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="local.settings.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      <CopyToPublishDirectory>Never</CopyToPublishDirectory>
    </None>
    <None Update="appsettings.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
  </ItemGroup>
</Project>
Enter fullscreen mode Exit fullscreen mode

This will update the Azure Functions runtime to v4, set the target framework to .NET 6 and then pin the NuGet packages to the versions that work!

That's all the changes we have to implement in the code.

Configure Azure Active Directory

The Azure function is already configured to expect authenticated calls. Out of the box, since we used the M.I.W extension, there is also an API permission that we need to configure. Now, you can go ahead and set up Azure AD manually, or you can use the nifty .NET Notebook in the project to programmatically set up everything for you.

Configure the Azure AD settings in the Function project

Open appsettings.json and add the App Registration information that was in the output of the Notebook from the previous step. Your appsettings.json should look like this:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "<your ad domain name>.onmicrosoft.com",
    "TenantId": "<your tenant id>",
    "ClientId": "<your API Client Id>",
    "CallbackPath": "/signin-oidc"
  },
 "Logging": {
   "LogLevel": {
     "Default": "Information",
     "Microsoft": "Warning",
     "Microsoft.Hosting.Lifetime": "Information"
   }
  }
}
Enter fullscreen mode Exit fullscreen mode

Save and close! You are now ready to run the Azure Function! In your terminal, type func host start. You should be presented with the following output that indicates that your Azure Function is up and running:

A running azure function

The default endpoint to call the API is: http://localhost:7071/api/SampleFunc

How does authentication work in this code?

Firstly, in Startup.cs we configure our authentication middleware in the ConfigureServices() method with the following code:

services.AddAuthentication(sharedOptions =>
{
    sharedOptions.DefaultScheme = Microsoft.Identity.Web.Constants.Bearer;
    sharedOptions.DefaultChallengeScheme = Microsoft.Identity.Web.Constants.Bearer;
})
.AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAd"));
Enter fullscreen mode Exit fullscreen mode

Then, inside the Function code, first we make sure that we have an authenticated request, i.e. we look for an auth header with Bearer tokenstring value. If there is no Auth header, the code will generate an HTTP 401 error. Secondly, we check the token claims to ensure that the user has the right api permission, in this instance access_as_user. If a valid token with the wrong permission or missing permissions is presented, the code will return a 403 error back to the client. If a valid token and the right permissions are present, the code executes as expected. The code that does this is here:

var (authenticationStatus, authenticationResponse) =
   await req.HttpContext.AuthenticateAzureFunctionAsync();
if (!authenticationStatus) return authenticationResponse;
        req.HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);
Enter fullscreen mode Exit fullscreen mode

NOTE: Auth attributes (a la ASP.NET) are supported but currently ignored. There will be a fix soon to fully support method-level attributes

Call the Azure Function from your client

Whichever client you use, you need to make sure that you can authenticate and get an Access Token to pass to your Azure Function. I use Thunder Client, which is a VS Code extension. These are my settings:

Thunder Client settings

We can now call our Function from our client. If everything was setup correctly, we should receive an HTTP 200 with a response that contains a greeting with the name of the signed in user.

Image description

We now have an HTTP endpoint built with .NET 6 and Azure Functions, all secured with Azure Active Directory in 3 lines of code!

Summary

There may still be a few small issues with the Microsoft.Identity.Web library and Azure Functions, but if you're willing to take a hard dependency on a couple of NuGet packages, then you get a lot for free using the out of the box tools. As always, make sure to reach out to us via Discord or email us at 425Show@microsoft.com if you have any questions!

Top comments (3)

Collapse
 
kadickerss profile image
Keith Dickerson

This worked the first try. I can see the user display name, the user ObjectID and the TenantID from within the Azure function. The access token was sent from a desktop app I had already created.

Outstanding!

Collapse
 
muehan profile image
André

Functionsv4 does not have a startup.cs

Collapse
 
david_kaye_73156b4ca1a2a1 profile image
David Kaye

Why did you not use AuthorizationLevel.User in the function?