Secure AspNetCore .NET 6 API with Auth0 - Works with SwaggerUI
Tags: net6, auth0, swagger, aspnetcore
Published: 2022-06-21 18:14:47

I am not going into great detail into the inner workings of JWT authentication in AspNetCore. 

But to get started we need to add a Nuget package that validates JWT tokens for us which is what Auth0 will return on successful login.

Change your working directory to the project where you want to add the authentication. Then run the following.

`dotnet add Microsoft.AspNetCore.Authentication.JwtBearer`

Next we will head over to our Program.cs file where we will add the following:

```csharp
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(options =>
    {
        options.Authority = "https://<auth0-domain>/";
        options.Audience = "https://<auth0-domain>/userinfo"; // or "<your audience>"
    });
```
As an illustration I've kept it simple but I advise using AspNetCore's configuration system to store the values.

Essentially, this is going to configure the AspNetCore pipeline to look for an a JWT in the `Authorization` header arriving who's value is `Bearer <auth0 access token jwt>`. The library we added is going to validate the JWT from Auth0 against our configuration.

Next we need to add two additional calls.

```csharp
app.UseAuthentication();
app.UseAuthorization();
```

```csharp
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
    options.AddSecurityDefinition("Auth0", new OpenApiSecurityScheme()
    {
        Type = SecuritySchemeType.OAuth2,
        Flows = new OpenApiOAuthFlows
        {
            AuthorizationCode = new OpenApiOAuthFlow()
            {
                // you need to set the urls as they are specific to Auth0
                AuthorizationUrl = new Uri("https://<auth0-domain>/authorize"),
                TokenUrl = new Uri("https://<auth0-domain>/oauth/token"),
                Scopes = new Dictionary<string, string>
                {
                    {"openid", "openid"},
                    {"email", "email"},
                    {"profile", "profile"},
                    // any additional custom scopes you want
                }
            }
        }
    });
    // optional if you are not using [Authorize] attributes
    options.OperationFilter<SecurityRequirementsOperationFilter>();
});
```
The operation filter is only required if you are using global authorization policies. For example: `app.MapControllers().RequireAuthorization(...)` I've included the class code at the end.

This next part is largely optional as I've described in the comments below. There are some additional values you can tweak on Swagger UI. 

Do watch out for the `audience` with Auth0. I spent many hours wondering why I was not getting back the right access token, until I saw this:

> In addition, if you have chosen to allow users to log in through an Identity Provider (IdP), such as Facebook, the IdP will issue its own access token to allow your application to call the IDP's API. For example, if your user authenticates using Facebook, the access token issued by Facebook can be used to call the Facebook Graph API. These tokens are controlled by the IdP and can be issued in any format. See Identity Provider Access Tokens for details. - [Auth0 - Access Tokens](https://auth0.com/docs/secure/tokens/access-tokens)

```csharp
// optional: only want to have Swagger UI available in Development 
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI(options =>
    {
        // optional: just prefills the Swagger UI input boxes, if left off you must supply these values. Would make sense to get these from configuration and better yet user-secrets 
        options.OAuthClientId("<auth0 client id>");
        options.OAuthClientSecret("<auth0 client secret>");
        // optional & gotcha: but if you are using Auth0 with a Social Connection you MUST supply an audience otherwise you will get back the underlying identity providers access token, NOT Auth0's
        options.OAuthAdditionalQueryStringParams(new Dictionary<string, string>
        {
            {"audience", "<your audience>"}
        });
    });
}
```

Now that everything is setup from a code perspective you can run you API and navigate to `/swagger/`. Now you should see an Authorize button.

![Swagger UI Authorize](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bmy1g31qa3j6clofz3rs.png)

You will need to configure Auth0 with the SwaggerUI callback url e.g `https://<your api url>/swagger/oauth2-redirect.html`

The final `Program.cs` file should look something like this:
https://gist.github.com/OliverRC/650436fbae77371a55b84c646c35ba3a

## Credit 

A huge shout out to his article which inspired most of what I've written about here: 

https://dotnetcoretutorials.com/2021/02/14/using-auth0-with-an-asp-net-core-api-part-3-swagger/

## Addendum - SecurityRequirementsOperationFilter 

```csharp
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()) return;
        if (anonControllerScope != null && anonControllerScope.Any()) return;

        // 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 is not present, the value is empty or value is not a valid jwt bearer token" });
        if (!operation.Responses.ContainsKey("403"))
            operation.Responses.Add("403", new OpenApiResponse { Description = "If user is not authorized to perform requested action" });
        
        var jwtAuthScheme = new OpenApiSecurityScheme
        {
            Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Auth0" }
        };
        
        operation.Security = new List<OpenApiSecurityRequirement>
        {
            new OpenApiSecurityRequirement
            {
                [ jwtAuthScheme ] = new List<string>()
            }
        };
    }
}
```