locked
Refresh JWT authentication token for multiple API calls? RRS feed

  • Question

  • User-1104215994 posted

    Hello guys,

    I am authenticating the client via JWT token. First of all, client sends a request to method A with its credentials. If client is valid, JWT token is send to the client. This token is valid for just 2 minutes. The client should send this token and some other data to method B in order to complete the process. Now there is a change in the design. I have to add another method for cancel/confirm. Client should call this new method to finish the transaction.  I think the client can not use the previous JWT token because it is used in method B and expired. So is there a way to reuse the same token? Is this possible? Or what is the preferred or best practice in this situation? How can I implement a refresh token logic into my code?

    Thank you.

    Sunday, May 12, 2019 6:43 PM

All replies

  • User-1038772411 posted

    Hi,cenk1536

    This is the very common problem for Microserices architecure and it is handled through API getway pattern. All the token validation should be handled at the API gateway level. After token validation, request should be forwarded to a (micro)service , that service can trust the request. If you have anything to update/fix/improve/add regarding token security, it's done in single one place.

    Reference Link :

    https://stackoverflow.com/questions/46308536/reusing-a-bearer-token-for-multiple-api-calls

    Monday, May 13, 2019 5:57 AM
  • User-1104215994 posted

    I am using asp.net web api2 and creating JWT token as follows and it works OK. How can I implement a refresh token mechanism in my code? Would you please help?

    Here is the handler:

    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 = "zdRhpFvSSjdG9n7";
                    
                    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;
            }
    
    
        }

    Here is the token creation in the controller:

    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 = "zdRhpFvSSjdG9n7";
                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;
            }

    How can I create <g class="gr_ gr_8 gr-alert gr_gramm gr_inline_cards gr_run_anim Grammar only-ins replaceWithoutSep" id="8" data-gr-id="8">refresh</g> token to my example?

    Monday, May 13, 2019 6:29 AM
  • User-1104215994 posted

    Any ideas how can I add refresh token in my code? Or should I need refresh token in my <g class="gr_ gr_24 gr-alert gr_spell gr_inline_cards gr_run_anim ContextualSpelling ins-del multiReplace" id="24" data-gr-id="24">scneario</g>?

    Here is my scenario:

    • In my <g class="gr_ gr_28 gr-alert gr_gramm gr_inline_cards gr_run_anim Punctuation only-ins replaceWithoutSep" id="28" data-gr-id="28">database</g> there is a users table which has user id, user name, hash, salt <g class="gr_ gr_29 gr-alert gr_gramm gr_inline_cards gr_run_anim Punctuation only-ins replaceWithoutSep" id="29" data-gr-id="29">and</g> password.
    • <g class="gr_ gr_31 gr-alert gr_gramm gr_inline_cards gr_run_anim Grammar only-ins doubleReplace replaceWithoutSep" id="31" data-gr-id="31">Client</g> sends <g class="gr_ gr_33 gr-alert gr_gramm gr_inline_cards gr_run_anim Grammar only-ins doubleReplace replaceWithoutSep" id="33" data-gr-id="33">request</g> to method A with user id and password which is provided to them
    • <g class="gr_ gr_35 gr-alert gr_gramm gr_inline_cards gr_run_anim Grammar only-ins doubleReplace replaceWithoutSep" id="35" data-gr-id="35">User</g> id and password <g class="gr_ gr_36 gr-alert gr_gramm gr_inline_cards gr_run_anim Grammar multiReplace" id="36" data-gr-id="36">is</g> checked if it is valid, access token created and send in response.
    • <g class="gr_ gr_32 gr-alert gr_gramm gr_inline_cards gr_run_anim Grammar only-ins doubleReplace replaceWithoutSep" id="32" data-gr-id="32">Client</g> sends <g class="gr_ gr_34 gr-alert gr_gramm gr_inline_cards gr_run_anim Grammar only-ins doubleReplace replaceWithoutSep" id="34" data-gr-id="34">request</g> to method B in order to complete the process with the given access token.
    • Now there is <g class="gr_ gr_30 gr-alert gr_gramm gr_inline_cards gr_run_anim Grammar multiReplace" id="30" data-gr-id="30"><g class="gr_ gr_22 gr-alert gr_spell gr_inline_cards gr_run_anim ContextualSpelling ins-del" id="22" data-gr-id="22">an other</g></g> method (cancel-<g class="gr_ gr_23 gr-alert gr_spell gr_inline_cards gr_run_anim ContextualSpelling ins-del multiReplace" id="23" data-gr-id="23">confim</g>) comes into play.
      • In order to call this new method directly, the access token should not be expired right?
      • If <g class="gr_ gr_41 gr-alert gr_gramm gr_inline_cards gr_run_anim Grammar only-ins replaceWithoutSep" id="41" data-gr-id="41">access</g> token is expired then <g class="gr_ gr_44 gr-alert gr_gramm gr_inline_cards gr_run_anim Grammar only-ins replaceWithoutSep" id="44" data-gr-id="44">client</g> has to make a new request for <g class="gr_ gr_42 gr-alert gr_gramm gr_inline_cards gr_run_anim Grammar only-ins replaceWithoutSep" id="42" data-gr-id="42">access</g> token and if the client is valid a brand new access token is provided. Since the <g class="gr_ gr_25 gr-alert gr_spell gr_inline_cards gr_run_anim ContextualSpelling ins-del multiReplace" id="25" data-gr-id="25">resouce</g> and <g class="gr_ gr_27 gr-alert gr_spell gr_inline_cards gr_run_anim ContextualSpelling" id="27" data-gr-id="27">aut</g> server is the same, I think I don't need for refresh token mechanism right? I couldn't see any benefit of <g class="gr_ gr_43 gr-alert gr_gramm gr_inline_cards gr_run_anim Grammar only-ins replaceWithoutSep" id="43" data-gr-id="43">refresh</g> token in my <g class="gr_ gr_26 gr-alert gr_spell gr_inline_cards gr_run_anim ContextualSpelling ins-del multiReplace" id="26" data-gr-id="26">senario</g>.

    Please share your ideas? I think I don't need refresh token, either way (refresh token or not) <g class="gr_ gr_128 gr-alert gr_gramm gr_inline_cards gr_run_anim Grammar only-ins replaceWithoutSep" id="128" data-gr-id="128">client</g> has to send a request.

    Tuesday, May 14, 2019 5:02 AM
  • User-474980206 posted

    it all in your security requirements. normally to refresh a token, you call a refresh api with the old token, and get a new one. this refresh api, could refresh an expired token if it was not too old. if you don't want to renew expired, then the calling app has two choices

    1) always call method a to get a new token to use in a second call.

    2) use a timer, and refresh the token before it's expired. it should handle that the refresh is denied, and call method a again before a calling another method.

    Wednesday, May 15, 2019 1:01 AM
  • User-1104215994 posted

    I don't see the difference in terms of <g class="gr_ gr_39 gr-alert gr_gramm gr_inline_cards gr_run_anim Grammar only-ins doubleReplace replaceWithoutSep" id="39" data-gr-id="39">number</g> of calls. I mean in my scenario, <g class="gr_ gr_164 gr-alert gr_gramm gr_inline_cards gr_run_anim Grammar only-ins replaceWithoutSep" id="164" data-gr-id="164">client</g> should make a request with <g class="gr_ gr_118 gr-alert gr_spell gr_inline_cards gr_run_anim ContextualSpelling multiReplace" id="118" data-gr-id="118">it's</g> <g class="gr_ gr_110 gr-alert gr_spell gr_inline_cards gr_run_anim ContextualSpelling ins-del multiReplace" id="110" data-gr-id="110">creneditals</g> in order to get the access token. Then with this access token, client <g class="gr_ gr_324 gr-alert gr_gramm gr_inline_cards gr_run_anim Grammar multiReplace" id="324" data-gr-id="324">send</g> request to cancel/<g class="gr_ gr_257 gr-alert gr_spell gr_inline_cards gr_run_anim ContextualSpelling ins-del multiReplace" id="257" data-gr-id="257">confim</g> method to complete the process. So in my <g class="gr_ gr_359 gr-alert gr_gramm gr_inline_cards gr_run_anim Punctuation only-ins replaceWithoutSep" id="359" data-gr-id="359">case</g> there is no point to implement a refresh token logic. Am I right? Am I missing something?

    Thursday, May 16, 2019 5:22 AM
  • User-474980206 posted

    the main use of a JWT, is when the caller does not have access to the username & password for each call, or the callee does not have access to the password for validation. if the caller will always have the users credentials, and the callee can always validate, then basic authentication may make more sense.

    you use a JWT to pass the user name and claims securely without including the password. the service can validate the token without needing the password (it just needs the shared encryption key). this is real common case with a single sign on server.  You could also use a JWT to cache the claims, if it is expensive to gather the claims. 

    Thursday, May 16, 2019 4:14 PM
  • User-1104215994 posted

    I am using customer id and password for extra measure. I will give customer id and password to the client upfront. With JWT, how would I know who is who?

    In my payload I couldn't see password:

    {
      "unique_name": "BIM",
      "nbf": 1558032733,
      "exp": 1558032853,
      "iat": 1558032733,
      "iss": "http://111.111.111.111:1907/api/v3/token",
      "aud": "http://111.111.111.111:1907/api/v3/token"
    }

    Thursday, May 16, 2019 6:48 PM
  • User475983607 posted

    cenk1536

    I am using customer id and password for extra measure. I will give customer id and password to the client upfront. With JWT, how would I know who is who?

    How is it possible that you do not know the client making the request if the client provides a username and password which exists in your system? 

    Thursday, May 16, 2019 7:08 PM
  • User-1104215994 posted

    my bad sorry, I thought <g class="gr_ gr_55 gr-alert gr_gramm gr_inline_cards gr_run_anim Grammar only-ins replaceWithoutSep" id="55" data-gr-id="55">password</g> is included.

    "you use a JWT to pass the user name and claims securely without including the password."

    How to parse JWT and check the user name?

    Thursday, May 16, 2019 7:40 PM
  • User475983607 posted

    "you use a JWT to pass the user name and claims securely without including the password."

    A JWT (JSON Web Token) is the result of a successful authentication.  

    How to parse JWT and check the user name?

    I recommend using the JWT API that comes with .NET.  If you want to roll your own JWT parser then you must read the openly published specs. 

    It is very difficult to understand how it is possible that you are using a JWT and looking into a refresh token without understanding how JWTs work.   Are you grabbing code from the Internet hoping something will magically work?

    https://jwt.io/introduction/

    https://jwt.io/

    Thursday, May 16, 2019 7:53 PM
  • User-474980206 posted

    JWT passes information in clear text (base64 encoded), but has an encrypted signature that guarantees that the data is not modified. typically a trusted service creates the JWT, and other services use the signature to verify that it has not been tampered with.

    a JWT token is 3 base64 strings separated by "."s

      <header>.<payload>.<signature>

    to get the data its:

    Func<string,string> Base64UrlDecode = s => 
    {
        var base64 = s.Replace('-','+').Replace('_','/');
        return System.Text.ASCIIEncoding.ASCII.GetString(System.Convert.FromBase64String(base64.PadRight(base64.Length + base64.Length % 3, '=')));
    };
    
    
    var jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
     	+ ".eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ"
     	+ ".SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
    
    var payload = Base64UrlDecode(jwt.Split('.')[1]);
    Console.WriteLine(payload);
    

    of course you should validate the signature.

    Friday, May 17, 2019 5:07 PM
  • User-1104215994 posted

    thank you for all the responses. I will stick with my solution right now. I don't need refresh token which was my first concern.

    Friday, May 17, 2019 5:21 PM