This post is part of a series on using Auth0 with an ASP.NET Core API, it’s highly recommended you start at part 1, even if you are only looking for something very specific (e.g. you came here from Google). Skipping parts will often lead to frustration as Auth0 is very particular about which settings and configuration pieces you need.
Part 1 – Auth0 Setup
Part 2 – ASP.NET Core Authentication
Part 3 – Swagger Setup
It’s very rare to build an API in .NET Core, and not use Swagger. After all, it’s the easiest self documenting tool available to developers, and provides a great way to test API’s without using a third party tool such as Postman. Setting up Swagger for general use is not really part of this article series, but we already have a previous article on the subject here : https://dotnetcoretutorials.com/2020/01/31/using-swagger-in-net-core-3/. If you are new to using Swagger, have a read as this piece of the Auth0 article series will cover setting up Swagger to work with Auth0, but not setting up Swagger itself!
With that out of the way, let’s jump right in.
Adding Auth0 Config To Swagger
In our startup.cs file, and inside the ConfigureServices method, we will have something similar to “AddSwaggerGen”. What we need to do is add a SecurityDefinition to Swagger. What this does is define how our API is authenticated, and how Swagger can authorize itself to make API calls. At a high level, it’s telling Swagger that “Hey, you need a token to call this API, here’s how to get one”.
The full code looks like so :
services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "API", Version = "v1", Description = "A REST API", TermsOfService = new Uri("https://lmgtfy.com/?q=i+like+pie") }); c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { Name = "Authorization", In = ParameterLocation.Header, Type = SecuritySchemeType.OAuth2, Flows = new OpenApiOAuthFlows { Implicit = new OpenApiOAuthFlow { Scopes = new Dictionary<string, string> { { "openid", "Open Id" } }, AuthorizationUrl = new Uri(Configuration["Authentication:Domain"] + "authorize?audience=" + Configuration["Authentication:Audience"]) } } }); });
What we are really adding is that SecurityDefinition. It’s somewhat beyond the scope of this article to really get into the nitty gritty of what each of these properties do, but this is the correct setup for Auth0. Also notice that our AuthorizationUrl is using our previous configuration that we set up to get .NET Core Authentication working.
Now move to the Configure method of your startup.cs. You need to modify your UseSwaggerUI call to look like so :
app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "API"); c.OAuthClientId(Configuration["Authentication:ClientId"]); });
Again, this is using a configuration variable that we set up earlier. All going well, if you open Swagger now, you should see a button saying Authorize at the top like so :
Clicking this and authenticating will redirect you back to Swagger, upon which you can make API calls that will send your bearer token.
If you get the following error :
It’s because you need to add your swagger URL (e.x. https://localhost:5001/swagger/oauth2-redirect.html) to the list of Allowed Callback URLs for your Auth0 application.
Now here’s where things diverge. If you are using the Authorize attribute on controllers (e.g. You have [Authorize] on top of every Controller class), then you are good to go. You should be able to tell because for each controller action inside Swagger, there will be a padlock icon indicating that authentication is required.
If you don’t see this padlock icon, it means that either you don’t have the correct Authorize attribute applied *or* you are using my method of applying Authorize globally. If it’s the former, then apply the Authorize attribute. If it’s the latter, continue reading below!
Adding SecurityRequirementsOperationFilter To Swagger
Swagger identifies which methods require authentication by looking for the [Authorize] attribute on controllers. But of course, if you are applying this globally as a convention like we mentioned earlier, this attribute won’t be there. So instead, we have to give Swagger a hand.
Add a class in your API project called “SecurityRequirementsOperationFilter”, and paste the following :
public class SecurityRequirementsOperationFilter : IOperationFilter { /// <summary> /// Applies the this filter on swagger documentation generation. /// </summary> /// <param name="operation"></param> /// <param name="context"></param> public void Apply(OpenApiOperation operation, OperationFilterContext context) { // then check if there is a method-level 'AllowAnonymous', as this overrides any controller-level 'Authorize' var anonControllerScope = context .MethodInfo .DeclaringType .GetCustomAttributes(true) .OfType<AllowAnonymousAttribute>(); var anonMethodScope = context .MethodInfo .GetCustomAttributes(true) .OfType<AllowAnonymousAttribute>(); // only add authorization specification information if there is at least one 'Authorize' in the chain and NO method-level 'AllowAnonymous' if (!anonMethodScope.Any() && !anonControllerScope.Any()) { // add generic message if the controller methods dont already specify the response type if (!operation.Responses.ContainsKey("401")) operation.Responses.Add("401", new OpenApiResponse { Description = "If Authorization header not present, has no value or no valid jwt bearer token" }); if (!operation.Responses.ContainsKey("403")) operation.Responses.Add("403", new OpenApiResponse { Description = "If user not authorized to perform requested action" }); var jwtAuthScheme = new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } }; operation.Security = new List<OpenApiSecurityRequirement> { new OpenApiSecurityRequirement { [ jwtAuthScheme ] = new List<string>() } }; } } }
This looks a bit over the top, but actually it’s just telling Swagger that unless it sees an “AllowAnonymous” attribute on an action or a controller, that we can assume it’s supposed to be authenticated. It’s essentially flipping things on it’s head and saying everything requires authentication unless I say so.
Now back in our ConfigureServices method of our startup.cs, we can go :
services.AddSwaggerGen(c => { //All the other stuff. c.OperationFilter<SecurityRequirementsOperationFilter>(); });
Which will of course add in our new filter to our swagger docs. This means that now, when we use Swagger, by default, all actions will require a JWT token. Perfect!
Hi,
Part 4 – Integration tests with jwt token?
Cheers
Yes, please!!