Mog0
Mog0

Reputation: 2129

How do I configure Scalar to authenticate through Entra?

I have a web app where the front-end authenticates using Microsoft Entra and uses the bearer token from that to authenticate with my backend APIs.

I have just removed Swagger / SwaggerUI from my app and replaced it with Microsoft's OpenAPI package and Scalar's ASP.NET package; Swagger wasn't fully functional, so I decided to upgrade and spend the time trying to get things working with the modern alternatives.

Scalar starts up and allows me to view all the backend APIs and send requests, but it doesn't know about the authentication so every request to an authenticated API (there are also a few that allow anonymous requests) returns a 401 response.

Can I configure OpenAPI / Scalar so that it knows which APIs require authentication and to authenticate the user properly allowing me to test my authenticated APIs.

At a previous job someone had configured SwaggerUI to do this, where clicking a couple of buttons would trigger authentication with Entra and then authenticate with the APIs we were testing; however, I don't actually know how they did this. This is the functionality I'd like to achieve through Scalar.

This is a personal (non-commercial) site I'm building for a club I'm a member of, for fun (no payment).

I'm using an ASP.NET Core 9 Web API backend and a Blazor WebAssembly frontend (which does authenticate properly).

I can't find any guides on how to do this, so I'm not sure if it's not possible or just too new to have many guides in general.

Update: After following the answer by Aaron FC below, I got Scalar authenticating with Entra. I had a couple of extra steps though:

My OpenAPI config is now:

builder.Services.AddOpenApi(options =>
{
    options.AddDocumentTransformer((document, context, cancellationToken) =>
    {
        document.Components ??= new OpenApiComponents();

        document.Components.SecuritySchemes.Add("oauth", new OpenApiSecurityScheme
        {
            Type = SecuritySchemeType.OAuth2,
            Flows = new OpenApiOAuthFlows
            {
                AuthorizationCode = new OpenApiOAuthFlow
                {
                    AuthorizationUrl = new Uri($"https://login.microsoftonline.com/{builder.Configuration["AzureAD:TenantId"]}/oauth2/v2.0/authorize"),
                    TokenUrl = new Uri("https://login.microsoftonline.com/{builder.Configuration["AzureAD:TenantId"]}/oauth2/v2.0/token"),
                    Scopes = new Dictionary<string, string>
                    {
                        {builder.Configuration["AzureAd:Scopes"], "Data Access"}
                    },
                    // To allow Scalar to select PKCE by Default
                    // valid options are 'SHA-256' | 'plain' | 'no'
                    Extensions = new Dictionary<string, IOpenApiExtension>()
                    {
                        ["x-usePkce"] = new OpenApiString("SHA-256")
                    }
                }
            }
        });

        return Task.CompletedTask;
    });
});

Upvotes: 3

Views: 229

Answers (1)

Aaron FC
Aaron FC

Reputation: 72

I struggled with finding documentation too but eventually came across a few things that pointed me in the right direction (linked below). You have to add a DocumentTransformer -> SecuritySchema using the AddOpenApi options:

Configure Open API

builder.Services.AddOpenApi(o =>
{
    o.AddDocumentTransformer((document, _, _) =>
    {
        document.Components ??= new OpenApiComponents();
        
        document.Components.SecuritySchemes.Add("oauth", new OpenApiSecurityScheme
        {
            Type = SecuritySchemeType.OAuth2,
            Flows = new OpenApiOAuthFlows
            {
                AuthorizationCode = new OpenApiOAuthFlow
                {
                    AuthorizationUrl = new Uri("<your-app-auth-endpoint>"),
                    TokenUrl = new Uri("<your-app-token-endpoint>"),
                    Scopes = new Dictionary<string, string>
                    {
                        {"api://<client-id>/data.read", "Read Data"}
                    },
                    // To allow Scalar to select PKCE by Default
                    // valid options are 'SHA-256' | 'plain' | 'no'
                    Extensions = new Dictionary<string, IOpenApiExtension>()
                    {
                        ["x-usePkce"] = new OpenApiString("SHA-256")
                    }
                    
                }
            }
        });
        
        return Task.CompletedTask;
    });
});

You can find the authorize/token endpoints by going to your Entra Admin Center -> Applications -> App Registrations -> (Selecting Your App) -> Endpoints (top banner at the time of writing) and then I used the OAuth 2.0 auth/token endpoints that should look something like:

https://login.microsoftonline.com/<directory-id>/oauth2/v2.0/authorize https://login.microsoftonline.com/<directory-id>/oauth2/v2.0/token

Wire-up Scalar with Defaults:

app.MapScalarApiReference(c =>
{
    c.WithOAuth2Authentication(o =>
    {
        o.ClientId = "<client-id>";
        o.Scopes = ["api://<client-id>/data.read"];
    });
});

We had to add a scope for our API app registration as using just User.Read did not work. You can do this by going to your app registration -> Expose an API and then adding a scope that will usually look something like: api://<client-id>/<scope-name>

You'll also want to register the redirect url for your client app registration in Entra as the url of the scalar client. In most cases it's something like https://localhost:<some-port>/scalar/v1

Once this is all configured, you can spin up the Scalar UI. There should be an Authentication box at the top with an Auth Type dropdown. You can select oauth and then ensure PKCE/Scopes are selected and click Authorize.

Shows a snippet of the Scalar authentication section on a black background

Shows a snippet of the Scalar authentication section with the oauth authorization type selected with prefilled data on a black background

Additional Context

We currently have two App Registrations in Entra. One is for our Frontend Angular SPA and the other is for our .NET Web API. The client-id we used was from the frontend App Registration while the scopes came from the backend App Registration.

Helpful Docs

Customize OpenAPI Documents

Scalar .NET Integration

IDX10511 Errors

SO Post 1

SO Post 2

Upvotes: 1

Related Questions