Integrate Azure AD in your Giraffe web app

Dotnet core, Azure AD, OAuth and openid connect are all exiting technologies. In this post I will combine them in a Giraffe web application.

Register your web app in Azure AD

To start, tell Azure AD that your web application will use AzureAD to authorize its users.
On https://portal.azure.com go to Azure Active Directory and click on App Registrations and finally on the "New application registration" button.

Now we need to provide some info.
I'll call the web app "DevProtocol.Giraffe.AuthDemo.Web". To make the debugging process easier, I'll put a localhost address in the sign-on URL. Dotnet core is using the signin-oidc path as default to handle the flow.

Take a note of the ApplicationID as we'll need it later on.

On the settings page you can go to keys to create a new secret key.
If you only need to authorize your user for your web app (without any api involved), you don't need to have a key.

In the Reply URLs section, you can verify that our signin-oidc path is added to the list of return URLs.

We're all set to start with our Giraffe web app.

Create a new giraffe web app.

dotnet new giraffe -V razor -lang F# --name DevProtocol.Giraffe.AuthDemo.Web  
cd src  
dotnet new sln --name DevProtocol.Giraffe.AuthDemo.Web  
dotnet sln add DevProtocol.Giraffe.AuthDemo.Web/DevProtocol.Giraffe.AuthDemo.Web.fsproj  

Configure giraffe to use oidc

You'll need to add some NuGet packages:

<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="2.1.1" />  
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.1.1" />  
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="2.1.1" />  
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.1.1" />  

We'll store our oidc settings in the appsettings.json file. A full description can be read in a previous post: https://www.devprotocol.com/use-appsettings-in-a-giraffe-web-app/.

Add a configureAppConfiguration method to tell Giraffe that you'll be using appsettings files:

let configureAppConfiguration (context:WebHostBuilderContext) (config: IConfigurationBuilder) =  
    config 
        .AddJsonFile("appsettings.json",false,true)
        .AddJsonFile(sprintf "appsettings.%s.json" context.HostingEnvironment.EnvironmentName,true)
        .AddEnvironmentVariables() |> ignore

Add it to your WebHostBUilder in the main function:

WebHostBuilder()  
        .UseKestrel()
        .UseContentRoot(contentRoot)
        .UseIISIntegration()
        .UseWebRoot(webRoot)
        .ConfigureAppConfiguration(configureAppConfiguration)
// Rest of the code

Now we need to add authentication to the services of our web app. First open the namespaces we'll need:

open Microsoft.AspNetCore.Authentication  
open Microsoft.AspNetCore.Authentication.Cookies  
open Microsoft.AspNetCore.Authentication.OpenIdConnect  

In our configureServices method we need to grab our configuration in order to read the oidc settings from our appsettings.json file:

let configureServices (services : IServiceCollection) =  
    let sp  = services.BuildServiceProvider()
    let env = sp.GetService<IHostingEnvironment>()
    let config = sp.GetService<IConfiguration>()

Next we need to add authentication to the services:

services.AddAuthentication(  
            Action<AuthenticationOptions>(fun auth ->
                auth.DefaultAuthenticateScheme <- CookieAuthenticationDefaults.AuthenticationScheme
                auth.DefaultChallengeScheme <- OpenIdConnectDefaults.AuthenticationScheme
                auth.DefaultSignInScheme <- CookieAuthenticationDefaults.AuthenticationScheme
            )
        )
        .AddCookie()
        .AddOpenIdConnect(
            Action<OpenIdConnectOptions>(fun oid ->
                config.GetSection("OpenIdConnect").Bind(oid)
            )) |> ignore 

Note that we bind our OpenIdConnect settings of our appsettings.json file to the OpenIdConnectOptions. The "OpenIdConnect" string must match the json setting that we'll setup in the next section.

Next we'll need to plug in our authentication middleware into the request pipeline. It's as simple as adding the UseAuthentication before UseGiraffe in the configureApp method:

let configureApp (app : IApplicationBuilder) =  
    let env = app.ApplicationServices.GetService<IHostingEnvironment>()
    (match env.IsDevelopment() with
    | true  -> app.UseDeveloperExceptionPage()
    | false -> app.UseGiraffeErrorHandler errorHandler)
        .UseCors(configureCors)
        .UseStaticFiles()
        .UseAuthentication()
        .UseGiraffe(routes)

The OpenIdConnect settings

Now add an appsettings.json file to your app and make sure it's copied to the output directory.
Your OpenIdConnect setting can be configured as follows:

"OpenIdConnect": {
        "ClientId": "<YOUR-APPLICATION-ID-FROM-AZURE-AD>",
        "Authority": "https://login.microsoftonline.com/<YOUR-AZURE-TENANT-ID>/",
        "CallbackPath": "/signin-oidc",
        "ResponseType": "id_token"
  • ClienId: this corresponds with the ApplicationID you received when you registered your app in AzureAD
  • Authority: Tells our app who's responsible to handle the authorization.
  • CallbackPath: This is the path you configured in the returnURL of your app registration. The oidc module will prefix the path with the url your app is running on (ex. https://localhost:56949/)
  • ResponseType: For more info about the ResponseType can be found on https://openid.net/specs/oauth-v2-multiple-response-types-10.html#idtoken. In our case an id_token is enough as we don't need an access token towards an API.

Secure an endpoint

The last thing we need to do is tell Giraffe which endpoint needs to be secured. Create a new method authorize that will challenge our OpenIdConnect configuration:

let authorize =  
    requiresAuthentication(challenge OpenIdConnectDefaults.AuthenticationScheme)

Now you can use the authorize function in your routes:

let routes: HttpFunc -> HttpFunc =  
    choose [
        GET >=>
            choose [
                route "/" >=> indexHandler "test"
                route "/secure" >=> authorize >=> handleGetSecure
            ]
        setStatusCode 404 >=> text "Not Found" 

Conclusion

If you understand how Azure AD and openid connect is working in a C# dotnet core application, it isn't that difficult to map everything into a Giraffe web app.

Download

Download the code from my github