Auth0 (https://auth0.com) remains one of the leaders in handling authentication and user management for sites. While it may seem odd to some to offload such a critical aspect of your application to a third party, the truth is, its not as far fetched as you think. Consider the popularity of touch to pay systems like Samsung Pay, Google Pay, and Apple Pay. Each of these (along with others) use a one time token exchange to allow payment. This enables payment information to be collected elsewhere lessening the impact in the event of a breach.
For this article, I wont dive to deeply into the intricacies of Auth0, its a very wide platform with a lot of functionality, a lot of which I am not an expert on. For my goal here, I wanted to show how I could use a Google and Username/Password login to access a service requiring a token and FURTHER how I could surface the permissions defined in Auth0 to facilitate authorization within our API (which uses .NET Core 3.1).
Authorization vs Authentication: Know the Difference
One of the key things that need to be understood when using Auth0 (or any identity provider) is the difference between Authentication and Authorization, in this case how it relates to APIs.
When we talk about Authentication we talk about someone have access to a system in a general sense. Usually, for an API, this is distinguished by the request having a token (JWT or otherwise) that is not expired and valid for the system. If this is not passed, or that which is passed is invalid the API shall return a 401 Unauthorized.
When we talk about Authorization we talk about whether someone CAN call an API endpoint. This is delineated through claims that are registered with the token. Information in these claims can be roles, permissions, or other data. When the given token does NOT supply the appropriate claims or data to access an endpoint the API shall return a 403 Forbidden.
Now that we understand that, let’s move on.
Establish a check for Authentication
There are multiple ways to go about this each with varying degrees of appropriateness. In my previous blog entry I did this through Kong (https://jfarrell.net/2020/06/11/kong-jwt-and-auth0/) and I will show it here in .NET Core as well. What is the difference? It comes down to what you are trying to do.
With Kong JWT plugin you can ideally require authentication across MANY APIs that are being fronted by the Kong Gateway. In general, its very common when you have microservices that requires a token to place them behind such a gateway that enforces the token being present – this way you do not need to configure and maintain the middleware for such an operation across multiple APIs that may be in different languages and even managed by disparate teams (or external teams).
.NET Core (as with other API frameworks) supports this for the API itself either via a custom connection or an identity provider, such as Auth0.
To enable this feature you need to add two NuGet packages:
The middleware is configured in the Startup.cs file via the IServiceCollection::UseAuthentication extension method. The following code does this and instructs the service to contact Auth0 to validates the tokens authenticity.
|options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;|
|options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;|
|options.Authority = Configuration["Auth0:Domain"];|
|options.Audience = Configuration["Auth0:Audience"];|
The first thing to notice here is the Auth0:Domain value which is the full URL of your Auth0 tenant (mine is farrellsoft). This domain informs the underlying mechanisms where to look for the OAuth endpoints.
The second thing is Auth0:Audience and this is more specific to the OAuth flow. The audience is, in this context, a validation parameter. That is to say, we want to validate that the token being received can access our API (which is the audience). This will map to some unique value that identifies your API. In my case, this is https://weatherforecast.com. I do not own that URL, it is used as a way to identify this API.
When we authenticate to our frontend application, we specify what audience we want. Assuming the frontend application has access to that audience, we will receive a token that we can pass with API calls.
This is another reason to look into a managed identity provider even beyond security. In high volume systems, the User API is constantly under load and can be a bottleneck if not thought through properly. You can imagine an architecture with MANY microservices, each needing to validate the token and get user information relevant to their task. Netflix has entire talks about this principle – with particular emphasis on the contribution of such services to cascade failures.
The final step is to enforce the presence of this token is the Authorize attribute on either the controller or specific endpoint method. Once you add this, not passing a valid JWT token will cause the middleware to return a 401 Unauthorized, as desired.
|public class WeatherForecastController : ControllerBase|
|private static readonly string Summaries = new|
|"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"|
|private readonly ILogger<WeatherForecastController> _logger;|
|public WeatherForecastController(ILogger<WeatherForecastController> logger)|
|_logger = logger;|
|public IEnumerable<WeatherForecast> Get()|
|var rng = new Random();|
|return Enumerable.Range(1, 5).Select(index => new WeatherForecast|
|Date = DateTime.Now.AddDays(index),|
|TemperatureC = rng.Next(–20, 55),|
|Summary = Summaries[rng.Next(Summaries.Length)]|
(Note: I am using the default controller implementation that comes out of the box with a new WebApi in .NET Core 3.1)
If you were to now, contact https://localhost:5001/weatherforecast in Postman (assuming you have SSL verification turned off in the settings) you will get a 401 (assuming you do not pass a token).
To generate a token that is valid, the easiest way is to create an SPA application in your Auth0 tenant and deploy a Quickstart (I recommend the Angular variant). Remember to have the the Web Console -> Network tab open as you login to this sample application – you can extract your token from the token endpoint call.
An additional bit of information is the site jwt.io which, given a token, can you show you all of the information contained within. Do not worry, nothing sensitive is exposed by default, just ALWAYS be mindful of what claims and properties you add to the token since they can be viewed here.
Establish a check for Authorization
While the authentication piece is commonly tied to a backend validation mechanism, authorization is commonly not the case, at least with JWT token. The reason is, we do not want to incur additional round trips if we can, safely, store that data in the token and have it decrypted.
This is an important aspect of this process because Authorization is ALWAYS the responsibility of the specific backend. There are many ways to accomplish it, but for this we are going to use .NET Core Authorization Requirement framework. This will allow us to inspect the valid token and indicate if certain requirements have been fulfilled. Based on this, we can create a policy for the user identified. Based on this policy the endpoint can be invoked or it cannot.
For this to work with Auth0 we need to ensure we create permissions and roles in the portal, enable RBAC and indicate we want assigned permissions for a specific audience (API) to be returned in the token.
First, we will need to create permissions:
Here I have created two permissions for my WeatherApi – read:weather and write:weather
Next, we need to assign these permissions to users. Note, this can also be done by assigning permissions to specific roles and assigning that role to that user. Roles are groups of permissions. This is all done in the Users and Roles section.
Here you can see the permissions we added can be assigned to this user.
Next, we need to enable RBAC and toggle on for Auth0 to send assigned permissions down with the user. This is done from within the API configuration under RBAC (Role Based Access Control) Settings.
Finally, we have to modify the way we get our token to be specific to our API (https://weatherforecast.com). That is the trick you see, the token is SPECIFIC to a particular API – this is what ensure that permissions with the same name do not enter into the same token.
Now, on this you may wonder, well how do I handle it with multiple services that have different names and permissions? To that I will say, there are ways but they get at the heart of what makes designing effective microservice architectures so challenging and are not appropriate to delve into in this entry.
Assuming you are using the Angular Quickstart you only need to ensure that, when the Login request is made, the audience is provided. As of the Quickstart I downloaded today (07/04/2020):
- Open the file at <root>/src/app/auth/auth.service.ts
- Insert a new line after Line 18 with the contents: audience: <your API identifier>
- For me that is going to be https://weatherforecast.com
Refresh your session either by revoking the user’s access in the Auth0 portal (under Authorized Applications) or simply by logging out. Log back in and recopy the token. Head over to jwt.io and run the token through – if everything is good, you will now see a permissions block in the decoded response.
You can now user this token in Postman or whatever to access the API and implement Authorization.
Building Authorization in .NET Core 3.1 WebApi
Now that the access token is coming into our backend we can analyze it for the qualities needed to “check” an authorization. In .NET Core this is handled via implementations of IAuthorizationRequirement and AuthorizationHandler<T> which work together to check the token for properties and validate fulfillment of policies.
We will start with implementing IAuthorizationRequirement – this class represents a desired requirement that we want to fulfill. In general it contains bits of information that the related handler will use to determine whether the requirement is fulfilled. Here is a sample of this Handler and Requirement working together:
|public class IsWeathermanAuthorizationHandler : AuthorizationHandler<IsWeathermanAuthorizationRequirement>|
|protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, IsWeathermanAuthorizationRequirement requirement)|
|var permission = context.User?.Claims?.FirstOrDefault(x => x.Type == "permissions" && x.Value == requirement.ValidPermission);|
|if (permission != null)|
|public class IsWeathermanAuthorizationRequirement : IAuthorizationRequirement|
|public string ValidPermission = "read:weather";|
Here, the token is passed to our IsWeathermanAuthorizationHandler where it looks for the permission as indicated by the requirement. If it finds it, it marks it as fulfilled. You can see the potential here for more sophisticated logic aimed at validating a requirement as fulfilled.
The final piece is the definition of a policy. A policy is a composition of requirements that need to be fulfilled to grant the user with the specific policy (by default ALL must be fulfilled but overloads can enable other functionality). In our example, we have created the IsWeatherman policy as such:
|options.AddPolicy("IsWeatherman", policy => policy.Requirements.Add(new IsWeathermanAuthorizationRequirement()));|
Notice the AuthorizationHandler is added via its interface as a singleton. Obviously use the instantiation strategy that makes the most sense. For our IsWeatherman policy to be fulfilled the single requirement (IsWeathermanAuthorizationRequirement) must be marked successful. This then allows the use of this policy via the Authorize attribute:
|[Authorize(Policy = "IsWeatherman")]|
|public class WeatherForecastController : ControllerBase|
Pretty neat eh? Now, you can simply decorate the controller class or action method and forgo any logic in the method. If the policy is not given to the user a 403 Forbidden will be returned.
What about multiple policies? As of right now, based on what I can find, this is NOT supported. But given the flexibility of this implementation, it would not be challenging to create composite policies and go about it that way.
To validate this functionality, our user from previously should have the permission and, as such, should be able to access your endpoint. To further verify, create a new user in Auth0 with no permissions, sign in, and use the token to access the endpoint, you should get a 403 Forbidden.
Congrats on getting this to work. If you have questions please leave a comment I will do my best to answer.