Authenticating on Swagger UI with OAuth2
Swagger (Open API) UI is a documentation and test tool that can be easily integrated into .NET WebApi.
It allows both consumers and developers to see what an API is doing, and the expected inputs and outputs for each endpoint. In my previous article about OAuth2 and WebApi I demonstrate how the API and authentication can be tested using Postman.
However, when you lock down your endpoints, and you try to execute a protected endpoint from the Swagger UI, you'll get a 'Forbidden 401' response. This is exactly what you want of course - it proves your API security is working!
Fortunately, Swagger UI does have a mechanism to add authentication to the Swagger UI, and thus allow the endpoint to be tested (and debugged).
Public Key Code Exchange (PKCE) flow
Unusually, I'm first going to show you what doesn't work.
Let's assume that the API's App Registration is set up as described in the article ASP.NET Web APIs, OAuth2 & Microsoft Identity . We intend to use Code Grant Flow with PKCE to access the API. This worked well with Postman, so we would expect it to work via Swagger UI too.
If you recall, both the Access Token (implicit flow) and Id Token (implicit and hybrid flow) checkboxes are clear.
The only change to the app registration is the addition of the default Swagger UI redirect URI to the Web platform, e.g. `https://localhost:7010/swagger/oauth2-redirect.html`.
Once this is done, we configure our Swagger UI to allow a permitted user to authenticate and then execute any endpoint, using PKCE.
builder.Services.AddSwaggerGen(options =>
{
var scopes = new Dictionary<string, string>
{
{ $"api://{builder.Configuration["AzureAd:ClientId"]}/.default", "Default" },
// add any custom scopes necessary, in place of or in addition to Default, e.g.
// { $"api://{builder.Configuration["AzureAd:ClientId"]}/access_as_user", "Access as User" },
{ "openid", "openid" },
};
var authFlow = new OpenApiOAuthFlow
{
AuthorizationUrl = new Uri($"{builder.Configuration["AzureAd:Instance"]}/consumers/oauth2/v2.0/authorize"),
TokenUrl = new Uri($"{builder.Configuration["AzureAd:Instance"]}consumers/oauth2/v2.0/token"),
Scopes = scopes,
};
var openApiSecurityScheme = new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows
{
AuthorizationCode = authFlow,
},
In = ParameterLocation.Header,
Scheme = "oauth2",
Name = "Authorization",
Description = "Use OAuth2 [Auth Code] authorization",
};
options.AddSecurityDefinition("OAuth2", openApiSecurityScheme);
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "OAuth2",
},
}, new List()
}
});
});
The UI now shows authentication links.
What I find is that it simply. Does. Not. Work . If I click any of the links, I get the authentication dialogue:
The first time I do this I get a permissions dialogue
If I accept that, then I eventually get the response saying authentication has failed.
If any reader has done PKCE on Swagger with Microsoft account please do get in touch.
The message is AADSTS90023: Cross-origin token redemption is permitted only for the 'Single-Page Application' client-type . I have tried a number of workarounds, including setting up the SPA platform in the WebApi App Registration with the required Swagger redirect url. Still no go. (Perhaps Swagger UI needs a separate SPA App Registration?)
If any reader has done this with Microsoft account please do get in touch. (Please note that I have got Swagger UI PKCE working with Entra Id authentication. Therefore I know that code very similar to the above does work with a non-Microsoft tenant using PKCE. The problem seems to boil down to Microsoft accounts.)
Implicit flow
So PKCE doesn't work. To get Swagger UI to authenticate, we have to enable Implicit flow by checking the Access token checkbox in the App Registration.
Additionally we have to make a minor tweak to the code where we define the OpenApiOAuthFlows ; we use the Implicit property instead of AuthorizationCode .
var openApiSecurityScheme = new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows
{
// AuthorizationCode = authFlow,
Implicit = authFlow, // use the Implicit property
},
In = ParameterLocation.Header,
Scheme = "oauth2",
Name = "Authorization",
Description = "Use OAuth2 [Implicit] authorization",
};
Now when the dialogue appears, we don't have to provide the client secret, but we are able to authenticate (note the logout button).
We can then execute the API endpoints and Swagger will automatically add the access token to the request Authorization header.
We can see that we've successfully called the endpoint, receiving an HTTP OK response.
Finally...
As a final touch, we can pre-populate the Client Id and pre-check the scopes in the authentication dialogue by adding the following code:
app.UseSwaggerUI(options =>
{
options.OAuthClientId(builder.Configuration["AzureAd:ClientId"]);
options.OAuthScopes(["openid", $"api://{builder.Configuration["AzureAd:ClientId"]}/.default"]);
});
Not having to manually enter the client id makes it much easier for users to quickly authenticate and test the endpoints on your API.
Hope this helps someone!