21

After a lot of reading, I have found a way to implement a custom JWT bearer token validator as below.

Starup.cs:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, 
         ILoggerFactory loggerFactory, IApplicationLifetime appLifetime)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();
        
    app.UseStaticFiles();
        
    app.UseIdentity();

    ConfigureAuth(app);
        
    app.UseMvcWithDefaultRoute();            
}

private void ConfigureAuth(IApplicationBuilder app)
{

    var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration.GetSection("TokenAuthentication:SecretKey").Value));


    var tokenValidationParameters = new TokenValidationParameters
    {
        // The signing key must match!
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = signingKey,
        // Validate the JWT Issuer (iss) claim
        ValidateIssuer = true,
        ValidIssuer = Configuration.GetSection("TokenAuthentication:Issuer").Value,
        // Validate the JWT Audience (aud) claim
        ValidateAudience = true,
        ValidAudience = Configuration.GetSection("TokenAuthentication:Audience").Value,
        // Validate the token expiry
        ValidateLifetime = true,
        // If you want to allow a certain amount of clock drift, set that here:
        ClockSkew = TimeSpan.Zero
    };

    var jwtBearerOptions = new JwtBearerOptions();
    jwtBearerOptions.AutomaticAuthenticate = true;
    jwtBearerOptions.AutomaticChallenge = true;
    jwtBearerOptions.TokenValidationParameters = tokenValidationParameters;
    jwtBearerOptions.SecurityTokenValidators.Clear();
    //below line adds the custom validator class
    jwtBearerOptions.SecurityTokenValidators.Add(new CustomJwtSecurityTokenHandler());
    app.UseJwtBearerAuthentication(jwtBearerOptions);
    
    var tokenProviderOptions = new TokenProviderOptions
    {
        Path = Configuration.GetSection("TokenAuthentication:TokenPath").Value,
        Audience = Configuration.GetSection("TokenAuthentication:Audience").Value,
        Issuer = Configuration.GetSection("TokenAuthentication:Issuer").Value,
        SigningCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256)
    };

    app.UseMiddleware<TokenProviderMiddleware>(Options.Create(tokenProviderOptions));
}

Custom validator class:

public class CustomJwtSecurityTokenHandler : ISecurityTokenValidator
{
    private int _maxTokenSizeInBytes = TokenValidationParameters.DefaultMaximumTokenSizeInBytes;
    private JwtSecurityTokenHandler _tokenHandler;

    public CustomJwtSecurityTokenHandler()
    {
        _tokenHandler = new JwtSecurityTokenHandler();
    }
    
    public bool CanValidateToken
    {
        get
        {
            return true;
        }
    }

    public int MaximumTokenSizeInBytes
    {
        get
        {
            return _maxTokenSizeInBytes;
        }

        set
        {
            _maxTokenSizeInBytes = value;
        }
    }

    public bool CanReadToken(string securityToken)
    {
        return _tokenHandler.CanReadToken(securityToken);            
    }

    public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken)
    {
        //How to access HttpContext/IP address from here?

        var principal = _tokenHandler.ValidateToken(securityToken, validationParameters, out validatedToken);

        return principal;
    }
}

In case of stolen token, I would like to add an additional layer of security to validate that the request is coming from the same client who generated the token.

Questions:

  1. Is there any way I can access HttpContext within the CustomJwtSecurityTokenHandler class so that I could add custom validations based on the current client/requestor?
  2. Is there any other way we can validate the authenticity of the requestor using such method/middleware?
Pang
  • 9,564
  • 146
  • 81
  • 122
Sang Suantak
  • 5,213
  • 1
  • 27
  • 46
  • what package did you need to add for UseIdentity and TokenValidationParameters? – schmidlop Aug 02 '17 at 20:05
  • @schmidlop `Microsoft.AspNetCore.Identity` and `Microsoft.IdentityModel.Tokens` respectively. – Sang Suantak Aug 03 '17 at 03:49
  • Since i couldn't find an answer anywhere, i moved the logic of validation pertaining to `HttpContext` to an `ActionFilter`. However, it does make the solution scattered. – Sang Suantak Jun 02 '17 at 03:14
  • You may find my answer here useful : https://stackoverflow.com/questions/47138849/how-to-correctly-get-dependent-scoped-services-from-isecuritytokenvalidator/57727839#57727839 – herostwist Aug 30 '19 at 13:52

3 Answers3

14

In ASP.NET Core, HttpContext could be obtained using IHttpContextAccessor service. Use DI to pass IHttpContextAccessor instance into your handler and get value of IHttpContextAccessor.HttpContext property.

IHttpContextAccessor service is not registered by default, so you first need to add the following in your Startup.ConfigureServices method:

services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();

then modify your CustomJwtSecurityTokenHandler class:

private readonly IHttpContextAccessor _httpContextAccessor;

public CustomJwtSecurityTokenHandler(IHttpContextAccessor httpContextAccessor)
{
    _httpContextAccessor = httpContextAccessor;
    _tokenHandler = new JwtSecurityTokenHandler();
}

... 

public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken)
{
    var httpContext = _httpContextAccessor.HttpContext;
}

You should also use DI technique for JwtSecurityTokenHandler instantiation. Look into Dependency Injection documentation if you are new to all this stuff.


Update: how to manually resolve dependencies (more info here)

modify Configure method to use IServiceProvider serviceProvider:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, 
         ILoggerFactory loggerFactory, IApplicationLifetime appLifetime,
         IServiceProvider serviceProvider)
{
    ...
    var httpContextAccessor = serviceProvider.GetService<IHttpContextAccessor>();
    // and extend ConfigureAuth
    ConfigureAuth(app, httpContextAccessor);
    ...
}
Pang
  • 9,564
  • 146
  • 81
  • 122
Set
  • 47,577
  • 22
  • 132
  • 150
  • I understand what you are trying to say. However, if you look at my code example, i need to pass an instance of `CustomJwtSecurityTokenHandler` during middleware injection. At this point, there is no `HttpContext`. Handling this scenario is the main problem i'm facing. – Sang Suantak Jun 02 '17 at 06:06
  • The problem is not that i can't access the `IHttpContextAccessor`. The problem is that when the `CustomJwtSecurityTokenHandler` is instantiated, `httpContextAccessor.Context` would be null. I have tried to implement your suggestion by moving my code around but have been unsuccessful so far. Once again, thanks for the suggestion. – Sang Suantak Jun 02 '17 at 07:21
  • 3
    @SangSuantak. Maybe now I understood you wrong, but: `when the CustomJwtSecurityTokenHandler is instantiated, httpContextAccessor.Context would be null` - yes, at this moment context does not exist, that's why during initialization you only need to store HttpContextAccessor instance, You should call `httpContextAccessor.HttpContext` in your `ValidateToken` method, as it will be called later when HTTP request comes and so HttpContext will be created. – Set Jun 02 '17 at 07:28
  • You are right, i was approaching the solution in the wrong way. I have successfully implemented your suggestion. Thanks a lot :D. – Sang Suantak Jun 02 '17 at 07:39
  • Note that the framework uses DI to call `Configure`, so you could simply add an `IHttpContextAccessor` arg to the method and avoid the `serviceProvider.GetService` lookup. – Todd Menier Mar 20 '18 at 21:36
  • 4
    @Set your example is incomplete. `jwtBearerOptions.SecurityTokenValidators.Add(new CustomJwtSecurityTokenHandler());` requires you to manually inject http context accessor when adding the validator – Konrad Sep 25 '18 at 08:41
2

Just to complement another solution and without injection into ISecurityTokenValidator, could be like

In your ISecurityTokenValidator Implementation (CustomJwtSecurityTokenHandler in this case)

public class CustomJwtSecurityTokenHandler : ISecurityTokenValidator {
   ...

   //Set IHttpContextAccessor as public property to set later in Starup class
   public IHttpContextAccessor _httpContextAccessor { get; set; };

   //Remove injection of httpContextAccessor;
   public CustomJwtSecurityTokenHandler()
   {
   _tokenHandler = new JwtSecurityTokenHandler();
   }

   ...

And in Startup class configure property "CustomJwtSecurityTokenHandler" as global member

public readonly CustomJwtSecurityTokenHandler customJwtSecurityTokenHandler = new()

In ConfigureServices method of Startup class add the global customJwtSecurityTokenHandler.

 public void ConfigureServices(IServiceCollection services)
 {

      ...

      services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
           .AddJwtBearer(
                o =>
                {
                    ...
                    //Add the global ISercurityTokenValidator implementation
                    o.SecurityTokenValidators.Add(this.customJwtSecurityTokenHandler );
                }
            );

      ...
} 

Then in Configure method of Startup class pass IHttpContextAccessor instance to property of the global customJwtSecurityTokenHandler (ISecurityTokenValidator)

public void Configure(IApplicationBuilder app, IHostingEnvironment env, 
         ILoggerFactory loggerFactory, IApplicationLifetime appLifetime,
         IServiceProvider serviceProvider)
{
    ...
    var httpContextAccessor = serviceProvider.GetService<IHttpContextAccessor>();
    //And add to property, and not by constructor
    customJwtSecurityTokenHandler.httpContextAccessor = httpContextAccessor;
    ...
}

In my case I've configured SecurityTokenValidator in ConfigureService so In this time there is not exist any instace of IServiceProvider, then in Configure method you can use IServiceProvider to get IHttpContextAccessor

Zaha
  • 846
  • 10
  • 21
1

For custom JWT validator, I created a JWTCosumerProvider class inhert to IOAuthBearerAuthenticationProvider. And implement the ValidateIdentity() method to check the identity Claim which i stored the client IP address at first place,then compare to current request Id address after.

public Task ValidateIdentity(OAuthValidateIdentityContext context)
    {

        var requestIPAddress = context.Ticket.Identity.FindFirst(ClaimTypes.Dns)?.Value;

        if (requestIPAddress == null)
            context.SetError("Token Invalid", "The IP Address not right");

        string clientAddress = JWTHelper.GetClientIPAddress();
        if (!requestIPAddress.Equals(clientAddress))
            context.SetError("Token Invalid", "The IP Address not right");


        return Task.FromResult<object>(null);
    }

JWTHelper.GetClientIPAddress()

internal static string GetClientIPAddress()
    {
        System.Web.HttpContext context = System.Web.HttpContext.Current;
        string ipAddress = context.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];

        if (!string.IsNullOrEmpty(ipAddress))
        {
            string[] addresses = ipAddress.Split(',');
            if (addresses.Length != 0)
            {
                return addresses[0];
            }
        }

        return context.Request.ServerVariables["REMOTE_ADDR"];
    }

hope this help!

Patrick
  • 21
  • 1
  • HI , could you tell me where should i use this code in my core api. i have set the ip address in the jwt toekn now i need to verify ip address of the requester with the jwt toekn value. thanks in advance. – Gagan Deep May 19 '20 at 14:53