locked
How to handle JwtSecurityToken in a load balancer environment? RRS feed

  • Question

  • User-1104215994 posted

    Hello guys,

    I have a web API 2 rest service. I implemented a JwtSecurityToken structure. When the client sends a request to my service, I am validating this client and send JwtSecurityToken in response. The other day, I was thinking about what should I do if there is a need for LB. I mean let's say 2 servers behind the load balancer in order to distribute the requests. For now, I deployed my rest API on a test server. But if I deploy my rest API on two servers which are behind the load balancer, is there any problem in terms of validating tokens? Should I implement another logic for creating/validating tokens?

    Here is my token creation code:

    public static string createToken(string username)
            {
                //Set issued at date
                DateTime issuedAt = DateTime.UtcNow;
                //set the time when it expires
                DateTime expires = DateTime.UtcNow.AddMinutes(2);
    
                var tokenHandler = new JwtSecurityTokenHandler();
    
                //create a identity and add claims to the user which we want to log in
                ClaimsIdentity claimsIdentity = new ClaimsIdentity(new[]
                {
                    new Claim(ClaimTypes.Name, username)
                });
    
                
                const string sec = "abc";
                var now = DateTime.UtcNow;
                var securityKey = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey(System.Text.Encoding.Default.GetBytes(sec));
                var signingCredentials = new Microsoft.IdentityModel.Tokens.SigningCredentials(securityKey, Microsoft.IdentityModel.Tokens.SecurityAlgorithms.HmacSha256Signature);
    
    
                //create the jwt
                var token =
                    (JwtSecurityToken)
                    tokenHandler.CreateJwtSecurityToken(issuer: "http://111.111.111.111:1907/api/v3/token", audience: "http://111.111.111.111:1907/api/v3/token",
                        subject: claimsIdentity, notBefore: issuedAt, expires: expires, signingCredentials: signingCredentials);
                var tokenString = tokenHandler.WriteToken(token);
    
                return tokenString;
            }

    And <g class="gr_ gr_17 gr-alert gr_gramm gr_inline_cards gr_run_anim Grammar only-ins replaceWithoutSep" id="17" data-gr-id="17">validation</g> code:

    internal class TokenValidationHandler : DelegatingHandler
        {
            private static bool TryRetrieveToken(HttpRequestMessage request, out string token)
            {
                token = null;
                IEnumerable<string> authzHeaders;
                if (!request.Headers.TryGetValues("Authorization", out authzHeaders) || authzHeaders.Count() > 1)
                {
                    return false;
                }
                var bearerToken = authzHeaders.ElementAt(0);
                token = bearerToken.StartsWith("Bearer ") ? bearerToken.Substring(7) : bearerToken;
                return true;
            }
    
            protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
            {
                HttpStatusCode statusCode;
                string token;
                //determine whether a jwt exists or not
                if (!TryRetrieveToken(request, out token))
                {
                    statusCode = HttpStatusCode.Unauthorized;
                    //allow requests with no token - whether a action method needs an authentication can be set with the claimsauthorization attribute
                    return base.SendAsync(request, cancellationToken);
                }
    
                try
                {
                    
                    const string sec = "abc";
                    
                    var now = DateTime.UtcNow;
                    var securityKey = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey(System.Text.Encoding.Default.GetBytes(sec));
    
    
                    SecurityToken securityToken;
                    JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
                    TokenValidationParameters validationParameters = new TokenValidationParameters()
                    {
                        ValidAudience = "http://111.111.111.111:1907/api/v3/token",
                        ValidIssuer = "http://111.111.111.111:1907/api/v3/token",
                        ValidateLifetime = true,
                        ValidateIssuerSigningKey = true,
                        LifetimeValidator = this.LifetimeValidator,
                        IssuerSigningKey = securityKey
                    };
                    //extract and assign the user of the jwt
                    Thread.CurrentPrincipal = handler.ValidateToken(token, validationParameters, out securityToken);
                    HttpContext.Current.User = handler.ValidateToken(token, validationParameters, out securityToken);
    
                    return base.SendAsync(request, cancellationToken);
                }
                catch (SecurityTokenValidationException e)
                {
                    statusCode = HttpStatusCode.Unauthorized;
                }
                catch (Exception ex)
                {
                    statusCode = HttpStatusCode.InternalServerError;
                }
                return Task<HttpResponseMessage>.Factory.StartNew(() => new HttpResponseMessage(statusCode) { });
            }
    
            public bool LifetimeValidator(DateTime? notBefore, DateTime? expires, SecurityToken securityToken, TokenValidationParameters validationParameters)
            {
                if (expires != null)
                {
                    if (DateTime.UtcNow < expires) return true;
                }
                return false;
            }
    
    
        }

    Wednesday, April 24, 2019 5:46 AM

All replies

  • User475983607 posted

    The main purpose of using a JWT is that it can be passed around.  It should not matter if the application is load balanced if you are following standard programming practices. 

    Wednesday, April 24, 2019 12:23 PM
  • User-1104215994 posted

    What do you mean by standard programming practices? Please be more clear. In my rest API, I am creating token and validation it. If there will be more than one IIS server, then this means there will be more than one token creation and validation code.

    Wednesday, April 24, 2019 7:48 PM
  • User475983607 posted

    If there will be more than one IIS server, then this means there will be more than one token creation and validation code.

    I do not understand you concern.  It should work as long as you are using the same Token Signing/Validation.  

    Wednesday, April 24, 2019 8:13 PM