简体   繁体   中英

aspnet core jwt token as get param

I'm working an an aspnet core 2 web api project, which main consumer is a vue web app.

Api uses jwt tokens as authentication method, and everything works fine.

Now i've put up all code to manage image storage and retrieval to/from database, but i have a problem in getting images out of db.

All routes (except login) is behind authentication, so to retrieve an image i have pass token within request header (as usually)

This prevents me from using image source tag to actually display the image, as in

<img src="/api/images/27" />

instead, i have to write some javascript code to request the image and place the content in the image tag, something like

// extracted from vue code (not my code, i'm the backend guy)
getImage() {
    this.$http.get('api/images/27', {headers: {'Authorization': 'Bearer ' + this.token},responseType: 'blob'})
    .then(response => {
        return response.blob()
    }).then(blob => { 
        this.photoUrl = URL.createObjectURL(blob)
    })
}

This works, but it's somehow an unnecessary complication.

I see in AspNet Core Identity that

Alternatively, you could obtain the token from somewhere else, such as a different header, or even a cookie. In that case, the handler will use the provided token for all further processing

(extracted from this article from Andre Lock blog) as you can also see checking aspnet core security code , where it says

Give application opportunity to find from a different location, adjust, or reject token

But i can't find any example on how to use this functionality and pass a custom token.

So, my question is: does anyone have any clue on how to pass a custom token (maybe read from a get parameter) to identity provider (maybe even only for some defined routes) ?


Thanks to serpent5 for his correct answer.

If anyone is interested, complete code for reading token from url param and passing it to validation is the following

service.AddAuthentication(...)
    .AddJwtBearer(options =>
        // ...
        options.Events = new JwtBearerEvents
        {
            OnMessageReceived = ctx =>
            {
                // replace "token" with whatever your param name is
                if (ctx.Request.Method.Equals("GET") && ctx.Request.Query.ContainsKey("token"))
                    ctx.Token = ctx.Request.Query["token"];
                return Task.CompletedTask;
            }
        };
    });

This can be handled using the JwtBearerEvents that is connected to the JwtBearerOptions instance provided to AddJwtBearer . Specifically, there's an OnMessageReceived event that can be implemented to provide the token itself. Here's an example:

services.AddAuthentication(...)
    .AddJwtBearer(jwtBearerOptions =>
    {
        // ...

        jwtBearerOptions.Events = new JwtBearerEvents
        {
            OnMessageReceived = ctx =>
            {
                // Access ctx.Request here for the query-string, route, etc.
                ctx.Token = "";
                return Task.CompletedTask;
            }
        };
    })

You can see how this is used in the source code :

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

// ...

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

Your application can provide this, by for example login in with the correct credentials. ( Frontend -> Login Correct -> Backend sends back the JWT Token.)

You then can store the token that your backend gave you in a cookie/localstorage.

Everytime you send a request back to the API, just retrieve your token from the cookie/localstorage and add it to the request header.

I'll show u an example of how to add middleware that will handle the token generation & validation.

appsettings.conf

{
  "Secret": {
    "Key": "abcdefghijklmnop123456789"
  }
}

the secret key is used to generate a unique JWT token, should be stored seperatly on the machine, this is just for example purposes

TokenProviderOptions.cs

public class TokenProviderOptions
{
    public string Path { get; set; } = "/token";
    public string Issuer { get; set; }
    public string Audience { get; set; }
    public TimeSpan Expiration { get; set; } = TimeSpan.FromHours(1);
    public SigningCredentials SigningCredentials { get; set; }
}

A class that will provide us with basic info for the token generation. The "path" can be changed to any path you want to retrieve the token.

TokenProviderMiddleware.cs

public class TokenProviderMiddleware
{
    private readonly RequestDelegate _next;
    private readonly TokenProviderOptions _options;
    private readonly IAccountService _accountService;

    public TokenProviderMiddleware(RequestDelegate next, IOptions<TokenProviderOptions> options, IAccountService accounteService)
    {
        _next = next;
        _options = options.Value;
        _accountService = accounteService;
    }

    public Task Invoke(HttpContext context)
    {
        //Check path request
        if (!context.Request.Path.Equals(_options.Path, StringComparison.Ordinal)) return _next(context);

        //METHOD: POST && Content-Type : x-www-form-urlencode
        if (context.Request.Method.Equals("POST") && context.Request.HasFormContentType)
            return GenerateToken(context);


        context.Response.StatusCode = 400;
        return context.Response.WriteAsync("Bad Request");
    }

    private async Task GenerateToken(HttpContext context)
    {
        var username = context.Request.Form["username"];
        var password = context.Request.Form["password"];

        var identity = await GetIdentity(username, password);

        if (identity == null)
        {
            context.Response.StatusCode = 400;
            await context.Response.WriteAsync("Invalid username or password");
            return;
        }

        var now = DateTime.UtcNow;

        var claims = new Claim[]
        {
            new Claim(JwtRegisteredClaimNames.Sub, username),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
            new Claim(JwtRegisteredClaimNames.Iat, now.Second.ToString(), ClaimValueTypes.Integer64)
        };

        var jwt = new JwtSecurityToken(
            issuer: _options.Issuer,
            audience: _options.Audience,
            claims: claims,
            notBefore: now,
            expires: now.Add(_options.Expiration),
            signingCredentials: _options.SigningCredentials);

        var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);

        var response = new
        {
            access_token = encodedJwt,
            expires_in = (int)_options.Expiration.TotalSeconds,
            username = username
        };

        context.Response.ContentType = "application/json";
        await context.Response.WriteAsync(JsonConvert.SerializeObject(response,
            new JsonSerializerSettings { Formatting = Formatting.Indented }));
    }

    private Task<ClaimsIdentity> GetIdentity(string username, string password)
    {
        //THIS STEP COULD BE DIFFERENT, I HAVE AN ACCOUNTSERVICE THAT QUERIES MY DB TO CHECK THE USER CREDENTIALS
        var auth = _accountService.Login(username, password).Result;
        return auth
            ? Task.FromResult(new ClaimsIdentity(new GenericIdentity(username, "Token"), new Claim[] { }))
            : Task.FromResult<ClaimsIdentity>(null);
    }
}

This is the middleware part. you've got to send a POST request with header type application/x-www-form-urlencoded and 2 fields username and password to the Path you've defined in your TokenProviderOptions .

If the checks pass, you will get a jwt token back.

And finally here's the Startup.cs

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; set; }

    public void ConfigureServices(IServiceCollection services)
    {
        //Mvc
        services.AddMvc();

        //...

        //Authentication
        services.AddAuthentication()
            .AddJwtBearer(jwt =>
            {
                var signingKey =
                    new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration.GetSection("Secret:Key").Value));

                jwt.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = signingKey,

                    ValidateIssuer = true,
                    ValidIssuer = "2CIssuer",

                    ValidateAudience = true,
                    ValidAudience = "2CAudience",

                    ValidateLifetime = true,

                    ClockSkew = TimeSpan.Zero
                };
            });

        //Authorization
        services.AddAuthorization(auth =>
        {
            auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme).RequireAuthenticatedUser().Build());
        });
    }

    public void Configure(IApplicationBuilder app)
    {
        //...

        //Authentication
        var signingKey =
            new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration.GetSection("Secret:Key").Value));

        var options = new TokenProviderOptions
        {
            Audience = "2CAudience",
            Issuer = "2CIssuer",
            SigningCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256)
        };

        app.UseAuthentication();

        //JWT
        app.UseMiddleware<TokenProviderMiddleware>(Options.Create(options));

        //Mvc
        app.UseMvc();
    }
}

I've left out the redundant code. This adds your custom middleware en configures the app to use JWT tokens.

All you have to do is change the custom params mentioned, sign your request with 'token': tokenValue and you're good!

I've a working backend template over here: https://github.com/BusschaertTanguy/dotnet_core_backend_template to double check everything.

Hope it helped!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM