How to pass JWT token as url query parameter in ASP.NET Core 6

By Mirek on (tags: ASP.NET, authentication, Authorization, JWT, categories: code, security, web)

Today I’ll show you how to pass a JWT Bearer authentication token to your ASP.NET Core 6.0 web application by appending it to the url query string. And all this is possible using just few lines of code

Let’s say we have a ASP.NET Core 6.0 based web API we want to protect using Json Web Token. Normally Bearer token is passed in a authorization herder like this:

Authorization = Bearer TOKEN_GOES_HERE

In our scenario, for some client application’s requirements though, the token must be passed as query string parameter. So we call our api in a url like so:

https://myapi.com/myendpoint?access_token=TOKEN_GOES_HERE

Now how can we accomplish this? Of course one way is to implement our custom AuthenticationHandler, with all its requirements including JWT token parsing, validation and so on. However there is another, simpler way.  First, we need to install nuget package that brings up all neccesary stuff. The package is called Microsoft.AspNetCore.Authentication.JwtBearer and is part of the ASP.NET Core framework. The source code of the framework is available on Github here.

Now in our Program.cs class we add following lines (these are only example of configuring the JWT token validation, use more restrictive conditions for production scenarios)

builder.Services.AddAuthentication(static o =>
{
o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(static o =>
{
o.TokenValidationParameters = new TokenValidationParameters
  {
      ValidateIssuer = false,
      ValidateAudience = false,
      ValidateIssuerSigningKey = true,
      IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("KEY_GOES_HERE")),
      ValidateLifetime = true
   };
});

Side note: for the brevity we skip here the authentication authority. We assume the user is authenticated and security token is generated by some another service. Also note that, in this example, we skip the Issuer and the Audience validation, assuming there is only one issuer and one audience. In such scenarios the token encryption signature validation is sufficient to ensure the token can be generated by the only issuer and validated by the only audience holding the same encryption key.

Now let’s take a look at the source code of JwtBearerHandler. Specifically lines 55- 66

//Give application opportunity to find from a different location, adjust, or reject token
var messageReceivedContext = new MessageReceivedContext(Context, Scheme, Options);

// event can set the token
await Events.MessageReceived(messageReceivedContext);

if (messageReceivedContext.Result != null)
{
   return messageReceivedContext.Result;
}

// If application retrieved token from somewhere else, use that.
token = messageReceivedContext.Token;

Yes! The framework team suggest that application may gather the token from a different location than authorization header. All we need to do is to set the Token property in  MessageReceivedContext that is being passed to the MessageReceived event. Let’s extend the AddJwtBearer configuration code and hook up to proper event.

.AddJwtBearer(static o =>
{
     …
     o.Events = new JwtBearerEvents
     {
         OnMessageReceived = static context =>
         {
             if (context.Request.Query.TryGetValue("access_token", out var token))
                 context.Token = token;
             return Task.CompletedTask;
         }
     }; });

That’ it! Now our api accepts the token in both, authorization header and query string.