ASP.NET Core APIs: Getting Swashbuckle to work with Auth0
Overview
Background
Using JWT bearer token authentication for your ASP.NET Core API projects is pretty standard stuff these days, and I've
done it many times - mostly with tokens from Duende IdentityServer.
When I tried to update the code and configuration to use Auth0 though, I kept getting a 401 response
after authenticating properly, with a message about an invalid_token
and the token itself was a JWT but it had an empty payload -- the part of the JWT that contains the claims.
tl;dr
I'll explain the full setup below, but make sure you send the audience query string parameter with your OAuth2
authentication request to the authorize endpoint (as shown in the OAuthAdditionalQueryStringParams method
below). A code repo that demonstrates this is here: https://github.com/dahlsailrunner/auth0-swashbuckle-api (but you need to provide your own Auth0 tenant and client id).
1app.UseSwagger()
2 .UseSwaggerUI(options =>
3 {
4 options.OAuthClientId(clientId);
5 options.OAuthAppName("QuickDemo");
6 options.OAuthUsePkce();
7 options.OAuthAdditionalQueryStringParams(new Dictionary<string, string>
8 {
9 { "audience", "https://yourapi-identifier-in-auth0" }
10 });
11 });
Setting Up an ASP.NET Core API for JWT Bearer Authentication
If you've created a new web api, setting it up to support bearer token authentication is pretty simple.
You need to add a NuGet package reference for Microsoft.AspNetCore.Authentication.JwtBearer as a first step.
Then in Program.cs add code that looks like the following:
1builder.Services
2 .AddAuthentication("Bearer")
3 .AddJwtBearer(options =>
4 {
5 options.Authority = "YOUR-AUTH0-TENANT";
6 options.Audience = "PLACEHOLDER FOR API IDENTIFIER";
7 });
You should probably also have the following in your Program.cs file a little further down for the HTTP
request pipeline which will require an authenticated user on calls to your API:
1 app
2 .UseAuthentication()
3 .UseRouting()
4 .UseAuthorization()
5 .UseEndpoints(endpoints =>
6 {
7 endpoints.MapControllers().RequireAuthorization();
8 });
Configuring the API within Auth0
Within the management area of Auth0, there is an Applications section, and that has an item called APIs.
If you go there and Create API you will be presented with a dialog that looks something like this:
In the Identifier section you can specify anything you like, but the value here will need to be
what you provide in the audience querystring parameter.
Once you have done this, make sure to update the Authority and Audience values of the AddJwtBearer call
above.
Testing the authentication to your API
If you have created an API within Auth0 and updated the Authority and Audience values in your code/configuration,
you can test the authorization in your API.
Make a simple GET request to your API and it should return a 401 since you are not passing a JWT bearer token
in the HTTP headers yet.
Then go to the Test page in the Auth0 API settings for your API as shown below:
The Response section will show an access_token that you can use.
Using a tool like [Postman] or [Insomnia] or anything else, send the same GET request to your
API method but this time add an HTTP header called Authorization and set the value to Bearer ACCESS_TOKEN_VALUE
where you replace ACCESS_TOKEN_VALUE with the access_token from the test page. In the screen shot this
would be eyJh... (rest of token omitted).
This should return a successful response from your API, which means it is properly requiring authenticated users.
Configuring a Swagger Client Application in Auth0
If you want to test your API from the Swagger UI, you need to set up an Application within Auth0 to do that.
You should set up a Single Page Application for this. In the Applications section of Auth0 choose "Create Application" and choose the "Single Page Web Applications" option.
The Settings page should look something like this:
Note the Domain - this will be the Authority - it should be the same value you used for the
AddJwtBearer authority above.
Also note the Client ID - this will be the OAuth2 Client Id that you will need to provide
to some Swagger setup below.
In the Application URIs section, provide the Callback URL for your API. It will be the base address
of your API with /oauth2-redirect.html appended, so something like this:
1 https://localhost:44380/oauth2-redirect.html
Connecting Swagger UI Authentication to Auth0
Using the Swagger UI to test your API is very handy, and relatively easy to set up.
You may want your Swagger UI (and even the Swagger doc itself) available in non-production
environments. If this is the case just make sure to wrap your UseSwagger method calls with
an environment check.
For my purposes, I find that having the code related to Swagger in its own folder can be
helpful, and I've created two extension methods (AddSwaggerFeatures and UseSwaggerFeatures)
that I call from Program.cs - and they are shown in the listing below (which you can expand with
the ellipsis "...") - note that they support both authentication and versioned APIs:
1public static class SwaggerExtensions
2{
3 public static IServiceCollection AddSwaggerFeatures(this IServiceCollection services)
4 {
5 services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
6 services.AddSwaggerGen();
7
8 return services;
9 }
10
11 public static IApplicationBuilder UseSwaggerFeatures(this IApplicationBuilder app, IConfiguration config,
12 IApiVersionDescriptionProvider provider, IWebHostEnvironment env)
13 {
14 if (!env.IsDevelopment())
15 {
16 return app;
17 }
18
19 var clientId = config.GetValue<string>("Authentication:SwaggerClientId");
20 app
21 .UseSwagger()
22 .UseSwaggerUI(options =>
23 {
24 foreach (var description in provider.ApiVersionDescriptions)
25 {
26 options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json",
27 $"QuickDemo API {description.GroupName.ToUpperInvariant()}");
28 options.RoutePrefix = string.Empty;
29 }
30
31 options.DocumentTitle = "QuickDemo Documentation";
32 options.OAuthClientId(clientId);
33 options.OAuthAppName("QuickDemo");
34 options.OAuthUsePkce();
35 options.OAuthAdditionalQueryStringParams(new Dictionary<string, string>
36 {
37 { "audience", config.GetValue<string>("Authentication:ApiName") }
38 });
39 });
40
41 return app;
42 }
43}
A key ingredient of the above code (you need to expand the listing to see it) is the inclusion
of the audience query string parameter when making the authorize request to Auth0. It's these
three lines:
1options.OAuthAdditionalQueryStringParams(new Dictionary<string, string>
2{
3 { "audience", config.GetValue<string>("Authentication:ApiName") }
4});
The ConfigureSwaggerOptions class is a second file that I generally use and it has the
security definitions that will be used - and it reads configuration to drive it. The
listing is probably easier to see on GitHub.
The configuration that both of the Swagger-related files read is here:
1 "Authentication": {
2 "Authority": "YOUR_AUTH0_TENANT - https://tenant.us.auth0.com",
3 "ApiName": "https://dsr-sampleapi",
4 "SwaggerClientId": "AUTH0_SINGLE_PAGE_CLIENTID",
5 "AdditionalScopes": "openid profile email read:everything"
6 }
Once you've got some code at least somewhat like this, you should be able to run your API and see
a Swagger UI that includes an Authorize button, and when you click it the dialog you see
should look something like this:
The client_secret can be left blank as it is not used in this particular
authorization flow (Code with PKCE). You should choose select all to choose all of the
scopes.
Once you complete the authorization, you should be back on the Swagger page and you
can execute API methods. When you do that, you should see a curl command that includes
the bearer token that was issued when you logged in:
You can copy that bearer token value - the whole thing, it's long - into your clipboard and paste the value into JWT.io
Your API call should have been successful, and if you put a breakpoint in the controller
and look at the User property, you should see claims that match the payload section shown
above.
Awesome!
Going Further
There is an additional scope in the token that I was showing above called read:everything and
this was something I configured both in the API defined in Auth0 as a "permission" that can
be associated with it, and in the requested scope from the application. You could wire up
some permissions in the ASP.NET Core API to make sure that this scope is allowed on the GET
methods of your API.
Hope this was helpful!