asp.net core JWT in uri query parameter?

JohnC picture JohnC · Aug 18, 2017 · Viewed 7.9k times · Source

I have an api that is protected by JWT and Authorize attribute and at the client I use jquery ajax call to deal with it.

This works fine, however I now need to be able to secure downloading of files so I can't set a header Bearer value, can it be done in the URI as an url parameter?

=-=-=-=-

UPDATE: This is what I ended up doing for my scenario which is an in-house project and very low volume but security is important and it might need to scale in future:

When user logs in I generate a random download key and put it in their user record in the db along with the expiry date of their JWT and return the download key to the client. The download route is protected to only allow a download if there is a query parameter that has the download key and that key exists in the user records and that expiry date has not passed. This way the dl key is unique per user, valid as long as the user's auth session is valid and can be revoked easily.

Answer

Technetium picture Technetium · Nov 14, 2018

This is a common problem.

Whenever you want to reference images or other files directly from an API in a single page application's HTML, there isn't a way to inject the Authorization request header between the <img> or <a> element and the request to the API. You can sidestep this by using some fairly new browser features as described here, but you may need to support browsers that lack this functionality.

Fortunately, RFC 6750 specifies a way to do exactly what you're asking via the "URI Query Parameter" authentication approach. If you follow its convention, you would accept JWTs using the following format:

https://server.example.com/resource?access_token=mF_9.B5f-4.1JqM&p=q

As stated in another answer and in RFC 6750 itself, you should be doing this only when necessary. From the RFC:

Because of the security weaknesses associated with the URI method (see Section 5), including the high likelihood that the URL containing the access token will be logged, it SHOULD NOT be used unless it is impossible to transport the access token in the "Authorization" request header field or the HTTP request entity-body.

If you still decide to implement "URI Query Parameter" authentication, you can use the Invio.Extensions.Authentication.JwtBearer library and call AddQueryStringAuthentication() extension method on JwtBearerOptions. Or, if you want to do it manually, you can certainly do that as well. Here's a code sample that shows both ways as extensions of the Microsoft.AspNetCore.Authentication.JwtBearer library.

public void ConfigureServices(IServiceCollection services) {
    services
        .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(
            options => {
                var authentication = this.configuration.GetSection("Authentication");

                options.TokenValidationParameters = new TokenValidationParameters {
                    ValidIssuers = authentication["Issuer"],
                    ValidAudience = authentication["ClientId"],
                    IssuerSigningKey = new SymmetricSecurityKey(
                        Encoding.UTF8.GetBytes(authentication["ClientSecret"])
                    )
                };

                // OPTION 1: use `Invio.Extensions.Authentication.JwtBearer`

                options.AddQueryStringAuthentication();

                // OPTION 2: do it manually

                options.Events = new JwtBearerEvents {
                    OnMessageReceived = (context) => {
                        StringValues values;

                        if (!context.Request.Query.TryGetValue("access_token", out values)) {
                            return Task.CompletedTask;
                        }

                        if (values.Count > 1) {
                            context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
                            context.Fail(
                                "Only one 'access_token' query string parameter can be defined. " +
                                $"However, {values.Count:N0} were included in the request."
                            );

                            return Task.CompletedTask;
                        }

                        var token = values.Single();

                        if (String.IsNullOrWhiteSpace(token)) {
                            context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
                            context.Fail(
                                "The 'access_token' query string parameter was defined, " +
                                "but a value to represent the token was not included."
                            );

                            return Task.CompletedTask;
                        }

                        context.Token = token;

                        return Task.CompletedTask;
                    }
                };
            }
        );
}