Require Auth for OpenAPI and Scalar/Swagger in .NET 10 APIs
How to secure the OpenApi schema and Scalar browser in a Microsoft Asp Net Core Web Api using Microsoft Identity Web for authentication.
Intro
In my previous post I showed how to setup OpenApi and Scalar in a Microsoft Asp Net Core Web Api. In this post I will show how to secure the OpenApi schema and Scalar browser using Microsoft Identity Web for authentication.
The OpenApi schema and Scalar browser can contain sensitive information about your API, such as the available endpoints, full documentation, request and response schemas, and authentication requirements.
Microsoft recommends OpenApi enabled only in development environments. But securing the OpenApi schema allows you to safely expose these resources in production environments as well, without risking unintended information disclosure to unauthorized users.
Prerequisites
A dotnet Asp Net Core 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 usedv2.12.41) (Note, alternatives likeswagger-uimay also work.)Microsoft.AspNetCore.OpenApi(I usedv10.0.3)
Setting up OpenApi and Scalar
See the guide in my previous post: Microsoft OpenApi Setup with Scalar
Authentication configuration
Assuming your website has setup authentication with Microsoft Identity Web, your setup looks something like this:
builder.Services.AddMicrosoftIdentityWebApiAuthentication(builder.Configuration)
.EnableTokenAcquisitionToCallDownstreamApi()
.AddDownstreamApi(
DownstreamApiService.DownstreamApiServiceName,
builder.Configuration.GetSection(
$"DownstreamApis:{DownstreamApiConfig.DownstreamApiName}"
)
)
.AddInMemoryTokenCaches();
// Snip ...
var app = builder.Build();
// Snip ...
app.UseAuthentication();
app.UseAuthorization();
// Snip ...
app.MapOpenApi();
app.MapScalarApiReference("/scalar", options => ...);
// Snip ...
Adding multiple Authentication Schemes
As the WebApi authentication setup is designed for APIs, it uses the JwtBearer authentication scheme by default. This is not suitable for authenticating users in the browser, as it does not support interactive login flows. To authenticate users in the browser, we need to use the OpenIdConnect authentication scheme, which is designed for web applications and supports interactive login flows. This is easily added by adding the AddMicrosoftIdentityWebAppAuthentication method to the services, which adds the OpenIdConnect authentication scheme.
First out:
Add a callbackPath to your authentication setup.
// in your appsettings.json
"AzureAd": {
// Snip other AzureAd config
"CallbackPath": "/signin-oidc",
}
You may have to add redirect URIs in your Azure AD app registration for the OpenIdConnect authentication to work. The redirect URI should be in the format https://<your-api-base-url>/signin-oidc.
Then add the AddMicrosoftIdentityWebAppAuthentication (NOTE: App, not Api) to your services:
builder.Services.AddMicrosoftIdentityWebApiAuthentication(
// Snip (see above)
)
// Note: IdentityWebApp, not Api! Otherwise identical to the Api setup (You may not require downstream api setup here though, depending on your use-case)
builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration)
.EnableTokenAcquisitionToCallDownstreamApi()
.AddDownstreamApi(
DownstreamApiService.DownstreamApiServiceName,
builder.Configuration.GetSection(
$"DownstreamApis:{DownstreamApiConfig.DownstreamApiName}"
)
)
.AddInMemoryTokenCaches();
We now have two authentication schemes, one for the API and one for the Web App. The Web App scheme is used by Scalar to authenticate users in the browser, and the API scheme is used to authenticate requests to the API.
We want to use the Api authentication for most routes, so we setup a default authentication scheme:
builder.Services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(
OpenIdConnectDefaults.AuthenticationScheme, // Optional: Allows us to use the OpenIdConnect cookie if authenticated
JwtBearerDefaults.AuthenticationScheme // This is the default scheme used for API authentication
)
.RequireAuthenticatedUser()
.Build();
});
Now we setup the Scalar Auth Policy: This has the OpenIdConnect scheme as default, which will cause it to redirect to the Azure AD login page when authentication is required.
var scalarUiAuthenticationPolicy = new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(
JwtBearerDefaults.AuthenticationScheme,
OpenIdConnectDefaults.AuthenticationScheme // Intentionally use OpenIdConnect last as it will redirect to Azure AD login page.
)
.AddRequirements(new DenyAnonymousAuthorizationRequirement())
.Build();
Then we have to setup the OpenApi schema to use the Web App authentication scheme, and require the correct scopes.
Add “RequireAuthorization” to the OpenApi endpoint to require authentication for the OpenApi document itself. This uses the “default” authentication scheme, which we set to the API scheme above.
app.MapOpenApi().RequireAuthorization(scalarUiAuthenticationPolicy);
Finally we add the RequireAuthorization to the Scalar endpoint
app.MapScalarApiReference(options =>
{
// Snip Scalar setup (see previous post)
})
.RequireAuthorization(scalarUiAuthenticationPolicy);
Fin
Good!
Now you should be able to run the API locally and see that a login is required to access the OpenApi document and the Scalar UI.
End
// Nils Henrik
This post was written by a human.