5 minute read

A quick guide to setting up OpenApi for Azure AD OAuth2 and integrating it with Scalar.

Prerequisites

A dotnet Web Api running .NET 10.

Existing authorization setup with OAuth2 (I’m using Microsoft.Identity.Web in this guide)

Install these nuget packages:

  • Scalar.AspNetCore (I used v2.10.3)
  • Microsoft.AspNetCore.OpenApi (I used v10.0.0)

Setting up OpenApi and Scalar

Following the Microsoft docs: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/overview and the Scalar docs: https://guides.scalar.com/scalar/scalar-api-references/integrations/net-aspnet-core/integration

Add the OpenApi service (this registers the code to generate the OpenApi schema):

services.AddOpenApi();

And register routes to serve the generated json: Also register the Scalar api here.

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi(); // Expose the openapi json
    app.MapScalarApiReference("/scalar"); // Enable Scalar on the "/scalar" route
}

You should now be able to start your API and find a OpenApi json at /openapi/v1.json and to view it visually in the /scalar route.

Configuring OAuth2

To configure OAuth2 for your API you need to do two things:

  1. Alter the OpenApi schema with custom changes.
  2. Setup Scalar to recognize the Authorization scheme, and configure some extra steps.

Altering the Open Api

We need to change the OpenApi initialization and add a Transformer

services.AddOpenApi(options =>
{
    options.AddDocumentTransformer<CustomOpenApiTransformer>();
});

The transformer is code to adjust the generated OpenApi schema. A short example of what a class could look like is below.

public class CustomOpenApiTransformer(IConfiguration configuration) : IOpenApiDocumentTransformer
{
    public Task TransformAsync(
        OpenApiDocument document,
        OpenApiDocumentTransformerContext context,
        CancellationToken cancellationToken
    )
    {
        document.Info = new OpenApiInfo
        {
            Title = "Echo AyelixProxyApi"
        };

        // Code from below should be placed here...
    }
}

Here we need to add two parts:

  1. A SecuritySchema
  2. A SecurityRequirement

SecuritySchema

The security schema describes a specific security setup.

For a dotnet api using Azure AD and OAuth2 this should be:

var authority = configuration.GetOrThrow("AzureAd:Authority"); // Learn more here https://learn.microsoft.com/en-us/entra/identity-platform/msal-client-application-configuration#authority
var audience = configuration.GetOrThrow("AzureAd:Audience"); // In this example this is the guid Client Id of the Api
var schemaKey = "OAuth2"; // This name is used as a key, and could be anything as long as you are consistent.
var scopes = new Dictionary<string, string>
{
    {
        $"{audience}/.default", // Actual Scope
        "All scopes" // Human readable description
    },
};
var securitySchemes = new Dictionary<string, IOpenApiSecurityScheme>
{
    [schemaKey] = new OpenApiSecurityScheme
    {
        Type = SecuritySchemeType.OAuth2,
        Scheme = "OAuth2",
        Flows = new OpenApiOAuthFlows
        {
            Implicit = new OpenApiOAuthFlow
            {
                AuthorizationUrl = new Uri($"{authority}/oauth2/v2.0/authorize"),
                TokenUrl = new Uri($"{authority}/oauth2/v2.0/token"),
                RefreshUrl = new Uri($"{authority}/oauth2/v2.0/token"),
                Scopes = scopes,
            },
        },
    },
};
document.Components ??= new OpenApiComponents();
document.Components.SecuritySchemes = securitySchemes;

Above we defined a schema, now we have to require this schema for the endpoints.

SecurityRequirement

A security requirement can be placed on every endpoint, or on the entire document. For our use-case we just put it on everything, and require the same scope for all endpoints. This could be adjusted according to your api design.

var securityRequirement = new OpenApiSecurityRequirement
{
    [new OpenApiSecuritySchemeReference(schemaKey, document)] = [..scopes.Keys],
};

document.Security = [securityRequirement];

Thats all. Insert this code in the TransformAsync method.

You now have the setup and your openapi.json file should have all the required mapping.

Setup Scalar

Finally we need to configure Scalar with the expected ClientId to use for the Web frontend.

app.MapScalarApiReference("/scalar", options =>
{
    options
        .AddPreferredSecuritySchemes("OAuth2") // This is the schemaKey from above
        .AddImplicitFlow(
            "OAuth2", // Again: schemaKey
            flow =>
            {
                flow.ClientId = builder.Configuration.GetOrThrow("Scalar:OAuthClientId"); // The ClientId to use. Can usually be `AzureAd:ClientId`
                var audience = builder.Configuration.GetOrThrow("AzureAd:Audience");
                flow.SelectedScopes = [$"{audience}/.default"]; // Same scopes as defined in the OpenApi transformer!
            }
        )
});

You may need to add a redirect Uri to your configuration now. By default the redirect Url should be http://localhost/scalar (Redirect uris to localhost do not have to include the port)

That’s all!

You can now browse and authenticate to your API in a quick little local setup!

End

// Nils Henrik

This post was written by a human.

Tags:

Categories:

Updated: