Securing a .NET Azure Function with Azure AD
Tags: tutorial, dotnet, serverless, azure
Published: 2022-01-25 23:39:07

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](https://github.com/AzureAD/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

{% youtube WRBXxyT70h4 %}

## Grab the code
The sample project from the stream is available to clone from [GitHub](https://github.com/425show/SecureAzureFunctionMiW)

## Prerequisites
- You need an Azure Subscription [Get it free here](https://azure.microsoft.com/free)
- An Azure Active Directory [Free M365 Developer](https://aka.ms/425Show/devenv)
- .NET 6 [Download here](https://dotnet.microsoft.com/en-us/download/dotnet/6.0)
- Identity extension tool for the .NET Core command line
- Azure Functions Core Tools [Install instructions](https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local?tabs=v4%2Cwindows%2Ccsharp%2Cportal%2Cbash#install-the-azure-functions-core-tools)

## 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>
``` 

## 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
```
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>
```
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](https://github.com/425show/SecureAzureFunctionMiW/blob/main/aad-setup.ipynb) 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"
   }
  }
}
```
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ec1o38dsv3akabdkuzty.png)

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"));
```
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);
```
>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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x9uu5izl720mhlx4jk5k.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fwnzhokgakrui0faf6u6.png)

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](425.show/discord) or email us at 425Show@microsoft.com if you have any questions!