• Handle Refresh Token Using ASP.NET Core 2.0 And JSON Web Token


    来源:
       https://www.c-sharpcorner.com/article/handle-refresh-token-using-asp-net-core-2-0-and-json-web-token/

    In this article , you will learn how to deal with the refresh token when you use jwt (JSON Web Token) as your access_token.

    Backgroud

    Many people choose jwt as their access_token when the client sends a request to the Resource Server.

    However, before the client sends a request to the Resource Server, the client needs to get the access_token from the Authorization Server. After receiving and storing the access_token, the client uses access_token to send a request to the Resource Server.

    But as all we know, the expired time for a jwt is too short. And we do not require the users to pass their name and password once more! At this time, the refresh_token provides a vary convenient way that we can use to exchange a new access_token.

    The normal way may be as per the following.

    ASP.NET Core

    I will use ASP.NET Core 2.0 to show how to do this work.

    Requirement first

    You need to install the SDK of .NET Core 2.0 preview and the VS 2017 preview.

    Now, let's begin!

    First of all, building a Resource Server

    Creating an ASP.NET Core Web API project.

    Edit the Program class to specify the url when we visit the API.

     1 public class Program  
     2 
     3 {  
     4 
     5 public static void Main(string[] args)  
     6 
     7     {  
     8 
     9         BuildWebHost(args).Run();  
    10 
    11     }  
    12 
    13 public static IWebHost BuildWebHost(string[] args) =>  
    14 
    15         WebHost.CreateDefaultBuilder(args)  
    16 
    17             .UseStartup<Startup>()  
    18 
    19             .UseUrls("http://localhost:5002")  
    20 
    21             .Build();  
    22 
    23 }  

     

    Add a private method in Startup class which configures the jwt authorization. There are some differences when we use the lower version of .NET Core SDK.

     1 public void ConfigureJwtAuthService(IServiceCollection services)  
     2 
     3 {  
     4 
     5 var audienceConfig = Configuration.GetSection("Audience");  
     6 
     7 var symmetricKeyAsBase64 = audienceConfig["Secret"];  
     8 
     9 var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64);  
    10 
    11 var signingKey = new SymmetricSecurityKey(keyByteArray);  
    12 
    13 var tokenValidationParameters = new TokenValidationParameters  
    14 
    15     {  
    16 
    17 // The signing key must match!  
    18 
    19         ValidateIssuerSigningKey = true,  
    20 
    21         IssuerSigningKey = signingKey,  
    22 
    23 // Validate the JWT Issuer (iss) claim  
    24 
    25         ValidateIssuer = true,  
    26 
    27         ValidIssuer = audienceConfig["Iss"],  
    28 
    29 // Validate the JWT Audience (aud) claim  
    30 
    31         ValidateAudience = true,  
    32 
    33         ValidAudience = audienceConfig["Aud"],  
    34 
    35 // Validate the token expiry  
    36 
    37         ValidateLifetime = true,  
    38 
    39         ClockSkew = TimeSpan.Zero  
    40 
    41     };  
    42 
    43     services.AddAuthentication(options =>  
    44 
    45     {  
    46 
    47         options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;  
    48 
    49         options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;  
    50 
    51     })  
    52 
    53     .AddJwtBearerAuthentication(o =>  
    54 
    55     {  
    56 
    57         o.TokenValidationParameters = tokenValidationParameters;  
    58 
    59     });  
    60 
    61 }  

    And, we need to use this method in the ConfigureServices method.

     1 public void ConfigureServices(IServiceCollection services)  
     2 
     3 {  
     4 
     5 //configure the jwt   
     6 
     7     ConfigureJwtAuthService(services);  
     8 
     9     services.AddMvc();  
    10 }  

    Do not forget touse the authentication in the Configure method.

     

     

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)  
    
    {  
    
        loggerFactory.AddConsole(Configuration.GetSection("Logging"));  
    
        loggerFactory.AddDebug();  
    
    //use the authentication  
    
        app.UseAuthentication();  
    
        app.UseMvc();  
    
    }  

     

     

    The last step of our Resource Server is to edit the ValueController so that we can use the authentication when we visit this API.

     
    [Route("api/[controller]")]  
    
    public class ValuesController : Controller  
    
    {  
    
    // GET api/values/5  
    
        [HttpGet("{id}")]  
    
        [Authorize]  
    
    public string Get(int id)  
    
        {  
    
    return "visit by jwt auth";  
    
        }          
    
    }  
     
    

      

    Turn to the Authentication Server

    How to design the authentication?

    Here is my point of view,

    When the client uses the parameters to get an access_token , the client needs to pass the parameters in the querystring are as follow:

    Parameter Value
    grant_type the value must be password
    client_id the client_id is assigned by manager
    client_secret the client_secret is assigned by manager
    username the name of the user
    password the password of the user


     When the client use the parameters to refresh a expired access_token , the client need to pass the parameters in the querystring are as follow,

    Parameter Value
    grant_type the value must be refresh_token
    client_id the client_id is assigned by manager
    client_secret the client_secret is assigned by manager
    refresh_token after authentication the server will return a refresh_token

    Here is the implementation!

    Create a new ASP.NET Core project and a new controller named TokenController.

      1 [Route("api/token")]  
      2 
      3 public class TokenController : Controller  
      4 
      5 {  
      6 
      7 //some config in the appsettings.json  
      8 
      9 private IOptions<Audience> _settings;  
     10 
     11 //repository to handler the sqlite database  
     12 
     13 private IRTokenRepository _repo;  
     14 
     15 public TokenController(IOptions<Audience> settings, IRTokenRepository repo)  
     16 
     17     {  
     18 
     19 this._settings = settings;  
     20 
     21 this._repo = repo;  
     22 
     23     }  
     24 
     25     [HttpGet("auth")]  
     26 
     27 public IActionResult Auth([FromQuery]Parameters parameters)  
     28 
     29     {  
     30 
     31 if (parameters == null)  
     32 
     33         {  
     34 
     35 return Json(new ResponseData  
     36 
     37             {  
     38 
     39                 Code = "901",  
     40 
     41                 Message = "null of parameters",  
     42 
     43                 Data = null  
     44 
     45             });  
     46 
     47         }  
     48 
     49 if (parameters.grant_type == "password")  
     50 
     51         {  
     52 
     53 return Json(DoPassword(parameters));  
     54 
     55         }  
     56 
     57 else if (parameters.grant_type == "refresh_token")  
     58 
     59         {  
     60 
     61 return Json(DoRefreshToken(parameters));  
     62 
     63         }  
     64 
     65 else  
     66 
     67         {  
     68 
     69 return Json(new ResponseData  
     70 
     71             {  
     72 
     73                 Code = "904",  
     74 
     75                 Message = "bad request",  
     76 
     77                 Data = null  
     78 
     79             });  
     80 
     81         }  
     82 
     83     }  
     84 
     85 //scenario 1 : get the access-token by username and password  
     86 
     87 private ResponseData DoPassword(Parameters parameters)  
     88 
     89     {  
     90 
     91 //validate the client_id/client_secret/username/passwo  
     92 
     93 var isValidated = UserInfo.GetAllUsers().Any(x => x.ClientId == parameters.client_id  
     94 
     95                                 && x.ClientSecret == parameters.client_secret  
     96 
     97                                 && x.UserName == parameters.username  
     98 
     99                                 && x.Password == parameters.password);  
    100 
    101 if (!isValidated)  
    102 
    103         {  
    104 
    105 return new ResponseData  
    106 
    107             {  
    108 
    109                 Code = "902",  
    110 
    111                 Message = "invalid user infomation",  
    112 
    113                 Data = null  
    114 
    115             };  
    116 
    117         }  
    118 
    119 var refresh_token = Guid.NewGuid().ToString().Replace("-", "");  
    120 
    121 var rToken = new RToken  
    122 
    123         {  
    124 
    125             ClientId = parameters.client_id,  
    126 
    127             RefreshToken = refresh_token,  
    128 
    129             Id = Guid.NewGuid().ToString(),  
    130 
    131             IsStop = 0  
    132 
    133         };  
    134 
    135 //store the refresh_token   
    136 
    137 if (_repo.AddToken(rToken))  
    138 
    139         {  
    140 
    141 return new ResponseData  
    142 
    143             {  
    144 
    145                 Code = "999",  
    146 
    147                 Message = "OK",  
    148 
    149                 Data = GetJwt(parameters.client_id, refresh_token)  
    150 
    151             };  
    152 
    153         }  
    154 
    155 else  
    156 
    157         {  
    158 
    159 return new ResponseData  
    160 
    161             {  
    162 
    163                 Code = "909",  
    164 
    165                 Message = "can not add token to database",  
    166 
    167                 Data = null  
    168 
    169             };  
    170 
    171         }  
    172 
    173     }  
    174 
    175 //scenario 2 : get the access_token by refresh_token  
    176 
    177 private ResponseData DoRefreshToken(Parameters parameters)  
    178 
    179     {  
    180 
    181 var token = _repo.GetToken(parameters.refresh_token, parameters.client_id);  
    182 
    183 if (token == null)  
    184 
    185         {  
    186 
    187 return new ResponseData  
    188 
    189             {  
    190 
    191                 Code = "905",  
    192 
    193                 Message = "can not refresh token",  
    194 
    195                 Data = null  
    196 
    197             };  
    198 
    199         }  
    200 
    201 if (token.IsStop == 1)  
    202 
    203         {  
    204 
    205 return new ResponseData  
    206 
    207             {  
    208 
    209                 Code = "906",  
    210 
    211                 Message = "refresh token has expired",  
    212 
    213                 Data = null  
    214 
    215             };  
    216 
    217         }  
    218 
    219 var refresh_token = Guid.NewGuid().ToString().Replace("-", "");  
    220 
    221         token.IsStop = 1;  
    222 
    223 //expire the old refresh_token and add a new refresh_token  
    224 
    225 var updateFlag = _repo.ExpireToken(token);  
    226 
    227 var addFlag = _repo.AddToken(new RToken  
    228 
    229         {  
    230 
    231             ClientId = parameters.client_id,  
    232 
    233             RefreshToken = refresh_token,  
    234 
    235             Id = Guid.NewGuid().ToString(),  
    236 
    237             IsStop = 0  
    238 
    239         });  
    240 
    241 if (updateFlag && addFlag)  
    242 
    243         {  
    244 
    245 return new ResponseData  
    246 
    247             {  
    248 
    249                 Code = "999",  
    250 
    251                 Message = "OK",  
    252 
    253                 Data = GetJwt(parameters.client_id, refresh_token)  
    254 
    255             };  
    256 
    257         }  
    258 
    259 else  
    260 
    261         {  
    262 
    263 return new ResponseData  
    264 
    265             {  
    266 
    267                 Code = "910",  
    268 
    269                 Message = "can not expire token or a new token",  
    270 
    271                 Data = null  
    272 
    273             };  
    274 
    275         }  
    276 
    277     }  
    278 
    279 //get the jwt token   
    280 
    281 private string GetJwt(string client_id, string refresh_token)  
    282 
    283     {  
    284 
    285 var now = DateTime.UtcNow;  
    286 
    287 var claims = new Claim[]  
    288 
    289         {  
    290 
    291 new Claim(JwtRegisteredClaimNames.Sub, client_id),  
    292 
    293 new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),  
    294 
    295 new Claim(JwtRegisteredClaimNames.Iat, now.ToUniversalTime().ToString(), ClaimValueTypes.Integer64)  
    296 
    297         };  
    298 
    299 var symmetricKeyAsBase64 = _settings.Value.Secret;  
    300 
    301 var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64);  
    302 
    303 var signingKey = new SymmetricSecurityKey(keyByteArray);  
    304 
    305 var jwt = new JwtSecurityToken(  
    306 
    307             issuer: _settings.Value.Iss,  
    308 
    309             audience: _settings.Value.Aud,  
    310 
    311             claims: claims,  
    312 
    313             notBefore: now,  
    314 
    315             expires: now.Add(TimeSpan.FromMinutes(2)),  
    316 
    317             signingCredentials: new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256));  
    318 
    319 var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);  
    320 
    321 var response = new  
    322 
    323         {  
    324 
    325             access_token = encodedJwt,  
    326 
    327             expires_in = (int)TimeSpan.FromMinutes(2).TotalSeconds,  
    328 
    329             refresh_token = refresh_token,  
    330 
    331         };  
    332 
    333 return JsonConvert.SerializeObject(response, new JsonSerializerSettings { Formatting = Formatting.Indented });  
    334 
    335     }  
    336 
    337 }  

     

     

    Both above two scenarios only use one action , because the parameters are similar.

    When the grant_type is password ,we will create a refresh_token and store this refresh_token to the sqlite database. And return the jwt toekn to the client.

    When the grant_type is refresh_token ,we will expire or delete the old refresh_token which belongs to this client_id and store a new refresh_toekn to the sqlite database. And return the new jwt toekn to the client.

    Note

    I use a GUID as my refresh_token , because GUID is more easier to generate and manager , you can use a more complex value as the refresh token.

    At last , Create a console app to test the refresh token.

     
    class Program  
    
    {  
    
    static void Main(string[] args)  
    
        {  
    
            HttpClient _client = new HttpClient();  
    
            _client.DefaultRequestHeaders.Clear();  
    
            Refresh(_client);  
    
            Console.Read();  
    
        }  
      
    private static void Refresh(HttpClient _client)  
    
        {  
    
    var client_id = "100";  
    
    var client_secret = "888";  
    
    var username = "Member";  
    
    var password = "123";  
    
    var asUrl = $"http://localhost:5001/api/token/auth?grant_type=password&client_id={client_id}&client_secret={client_secret}&username={username}&password={password}";  
    
            Console.WriteLine("begin authorizing:");  
    
            HttpResponseMessage asMsg = _client.GetAsync(asUrl).Result;  
    
            string result = asMsg.Content.ReadAsStringAsync().Result;  
    
    var responseData = JsonConvert.DeserializeObject<ResponseData>(result);  
    
    if (responseData.Code != "999")  
    
            {  
    
                Console.WriteLine("authorizing fail");  
    
    return;  
    
            }  
    
    var token = JsonConvert.DeserializeObject<Token>(responseData.Data);  
    
            Console.WriteLine("authorizing successfully");              
    
            Console.WriteLine($"the response of authorizing {result}");              
    
            Console.WriteLine("sleep 2min to make the token expire!!!");  
    
            System.Threading.Thread.Sleep(TimeSpan.FromMinutes(2));  
    
            Console.WriteLine("begin to request the resouce server");  
    
    var rsUrl = "http://localhost:5002/api/values/1";  
    
            _client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token.access_token);  
    
            HttpResponseMessage rsMsg = _client.GetAsync(rsUrl).Result;  
    
            Console.WriteLine("result of requesting the resouce server");  
    
            Console.WriteLine(rsMsg.StatusCode);  
    
            Console.WriteLine(rsMsg.Content.ReadAsStringAsync().Result);  
    
    //refresh the token  
    
    if (rsMsg.StatusCode == HttpStatusCode.Unauthorized)  
    
            {  
    
                Console.WriteLine("begin to refresh token");  
    
    var refresh_token = token.refresh_token;  
    
                asUrl = $"http://localhost:5001/api/token/auth?grant_type=refresh_token&client_id={client_id}&client_secret={client_secret}&refresh_token={refresh_token}";  
    
                HttpResponseMessage asMsgNew = _client.GetAsync(asUrl).Result;  
    
                string resultNew = asMsgNew.Content.ReadAsStringAsync().Result;  
    
    var responseDataNew = JsonConvert.DeserializeObject<ResponseData>(resultNew);  
    
    if (responseDataNew.Code != "999")  
    
                {  
    
                    Console.WriteLine("refresh token fail");  
    
    return;  
    
                }  
    
                Token tokenNew = JsonConvert.DeserializeObject<Token>(responseDataNew.Data);  
    
                Console.WriteLine("refresh token successful");  
    
                Console.WriteLine(asMsg.StatusCode);  
    
                Console.WriteLine($"the response of refresh token {resultNew}");  
    
                Console.WriteLine("requset resource server again");  
    
                _client.DefaultRequestHeaders.Clear();  
    
                _client.DefaultRequestHeaders.Add("Authorization", "Bearer " + tokenNew.access_token);  
    
                HttpResponseMessage rsMsgNew = _client.GetAsync("http://localhost:5002/api/values/1").Result;  
    
                Console.WriteLine("the response of resource server");  
    
                Console.WriteLine(rsMsgNew.StatusCode);  
    
                Console.WriteLine(rsMsgNew.Content.ReadAsStringAsync().Result);  
    
            }  
    
        }  
    
    }  

     

    We should pay attention to the request of the Resource Server!

    We must add a HTTP header when we send a HTTP request : `Authorization:Bearer token`

    Now , using the dotnet CLI command to run our three projects.

    Here is the screenshot of the runninng result.

    ASP.NET Core

    Note

    • In the console app, I do not store the access_token and the refresh_token, I just used them once . You should store them in your project ,such as the web app, you can store them in localstorage.
    • When the access_token is expired , the client should remove the expired access_toekn and because the short time will cause the token expired , we do not need to worry about the leakage of the token !

    Summary

    This article introduced an easy way to handle the refresh_token when you use jwt. Hope this will help you to understand how to deal with the tokens.

  • 相关阅读:
    Quartz.NET-2.3.3 各种 数据库配置 类别大全
    C#获取当前路径的七种方法 【转载】
    BCB 如何拦截TAB键消息
    用union 和 struct 位域操作
    表值函数
    C#中 委托和事件的关系
    关于C++ Builder Codegurad 问题的排查。
    存储过程中使用事务的“正规”写法
    C++ 中对vector<T*> 数组的查找和排序
    BCB 中 Application->CreateForm 和 New 的一个区别
  • 原文地址:https://www.cnblogs.com/frank0812/p/11840877.html
Copyright © 2020-2023  润新知