Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create Refresh Token with External Login Provider?

Tags:

I have searched over the web and could not find a solution to my problem. I am implementing OAuth in my app. I am using ASP .NET Web API 2, and Owin. The scenario is this, once a user request to the Token end point, he or she will receive an access token along with a refresh token to generate a new access token. I have a class the helps me to generate a refresh token. Here is it :

   public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider     {          private static ConcurrentDictionary<string, AuthenticationTicket> _refreshTokens = new ConcurrentDictionary<string, AuthenticationTicket>();        public async Task CreateAsync(AuthenticationTokenCreateContext context)         {              var refreshTokenId = Guid.NewGuid().ToString("n");             using (AuthRepository _repo = new AuthRepository())             {                 var refreshTokenLifeTime = context.OwinContext.Get<string>                                    ("as:clientRefreshTokenLifeTime");                 var token = new RefreshToken()                  {                      Id = Helper.GetHash(refreshTokenId),                     ClientId = clientid,                      Subject = context.Ticket.Identity.Name,                     IssuedUtc = DateTime.UtcNow,                     ExpiresUtc = DateTime.UtcNow.AddMinutes(15)                 };                 context.Ticket.Properties.IssuedUtc = token.IssuedUtc;                 context.Ticket.Properties.ExpiresUtc = token.ExpiresUtc;                 token.ProtectedTicket = context.SerializeTicket();                 var result = await _repo.AddRefreshToken(token);                 if (result)                 {                             context.SetToken(refreshTokenId);                 }             }         }          // this method will be used to generate Access Token using the Refresh Token         public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)         {              string hashedTokenId = Helper.GetHash(context.Token);             using (AuthRepository _repo = new AuthRepository())             {                 var refreshToken = await _repo.FindRefreshToken(hashedTokenId);                 if (refreshToken != null )                 {                     //Get protectedTicket from refreshToken class                     context.DeserializeTicket(refreshToken.ProtectedTicket);                     // one refresh token per user and client                     var result = await _repo.RemoveRefreshToken(hashedTokenId);                 }             }         }          public void Create(AuthenticationTokenCreateContext context)         {             throw new NotImplementedException();         }          public void Receive(AuthenticationTokenReceiveContext context)         {             throw new NotImplementedException();         }     } 

now i am allowing my users to register through facebook. Once a user register with facebook, I generate an access token and give it to him. Should I generate a refresh token as well ? Onething comes to my mind, is to generate a long access token like one day, then this user has to login with facebook again. But if i do not want to do that, I can give the client, a refresh token, and he can use it to refresh the generated access token and get a new. How do I create the refresh token and attach it to the response when someone register or login with facebook or externally ?

Here is my external registration API

  public class AccountController : ApiController     {       [AllowAnonymous]       [Route("RegisterExternal")]       public async Task<IHttpActionResult> RegisterExternal(RegisterExternalBindingModel model)       {           if (!ModelState.IsValid)          {             return BadRequest(ModelState);          }          var accessTokenResponse = GenerateLocalAccessTokenResponse(model.UserName);          return Ok(accessTokenResponse);       }       } 

// Private method to generate access token

private JObject GenerateLocalAccessTokenResponse(string userName)         {              var tokenExpiration = TimeSpan.FromDays(1);             ClaimsIdentity identity = new ClaimsIdentity(OAuthDefaults.AuthenticationType);             identity.AddClaim(new Claim(ClaimTypes.Name, userName));             identity.AddClaim(new Claim("role", "user"));             var props = new AuthenticationProperties()             {                 IssuedUtc = DateTime.UtcNow,                 ExpiresUtc = DateTime.UtcNow.Add(tokenExpiration),             };             var ticket = new AuthenticationTicket(identity, props);             var accessToken = Startup.OAuthBearerOptions.AccessTokenFormat.Protect(ticket);             JObject tokenResponse = new JObject(                                         new JProperty("userName", userName),                                         new JProperty("access_token", accessToken),                                         // Here is what I need                                         new JProperty("resfresh_token", GetRefreshToken()),                                         new JProperty("token_type", "bearer"),                                         new JProperty("refresh_token",refreshToken),                                         new JProperty("expires_in", tokenExpiration.TotalSeconds.ToString()),                                         new JProperty(".issued", ticket.Properties.IssuedUtc.ToString()),                                         new JProperty(".expires", ticket.Properties.ExpiresUtc.ToString())         );             return tokenResponse;         } 
like image 740
user123456 Avatar asked Sep 09 '14 08:09

user123456


People also ask

How is refresh token created?

Get a refresh token. To get a refresh token, you send a request to your Okta Authorization Server. The only flows that support refresh tokens are the authorization code flow and the resource owner password flow.

Does client credentials support refresh token?

The token endpoint does not issue a refresh token as refresh tokens are not supported by the client credentials grant.

Can I use refresh token instead of access token?

Refresh Token are typically longer lived than Access Tokens and used to request a new Access Token without forcing user authentication. Unlike Access Tokens, Refresh Tokens are only used with the Authorization Server and are never sent to a web service.

Can refresh token be JWT?

Authentication is implemented through JWT access tokens along with refresh tokens. The API returns a short-lived token (JWT), which expires in 15 minutes, and in HTTP cookies, the refresh token expires in 7 days.


1 Answers

I spent a lot of time to find the answer to this question. So, i'm happy to help you.

1) Change your ExternalLogin method. It usually looks like:

if (hasRegistered) {      Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);       ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(UserManager,                 OAuthDefaults.AuthenticationType);      ClaimsIdentity cookieIdentity = await user.GenerateUserIdentityAsync(UserManager,                 CookieAuthenticationDefaults.AuthenticationType);       AuthenticationProperties properties = ApplicationOAuthProvider.CreateProperties(user.UserName);      Authentication.SignIn(properties, oAuthIdentity, cookieIdentity); } 

Now, actually, it is necessary to add refresh_token. Method will look like this:

if (hasRegistered) {      Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);       ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(UserManager,                    OAuthDefaults.AuthenticationType);      ClaimsIdentity cookieIdentity = await user.GenerateUserIdentityAsync(UserManager,                     CookieAuthenticationDefaults.AuthenticationType);       AuthenticationProperties properties = ApplicationOAuthProvider.CreateProperties(user.UserName);       // ADD THIS PART      var ticket = new AuthenticationTicket(oAuthIdentity, properties);      var accessToken = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket);                  Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext context =                      new Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext(                         Request.GetOwinContext(),                          Startup.OAuthOptions.AccessTokenFormat, ticket);       await Startup.OAuthOptions.RefreshTokenProvider.CreateAsync(context);      properties.Dictionary.Add("refresh_token", context.Token);       Authentication.SignIn(properties, oAuthIdentity, cookieIdentity); } 

Now the refrehs token will be generated.

2) There is a problem to use basic context.SerializeTicket in SimpleRefreshTokenProvider CreateAsync method. Message from Bit Of Technology

Seems in the ReceiveAsync method, the context.DeserializeTicket is not returning an Authentication Ticket at all in the external login case. When I look at the context.Ticket property after that call it’s null. Comparing that to the local login flow, the DeserializeTicket method sets the context.Ticket property to an AuthenticationTicket. So the mystery now is how come the DeserializeTicket behaves differently in the two flows. The protected ticket string in the database is created in the same CreateAsync method, differing only in that I call that method manually in the GenerateLocalAccessTokenResponse, vs. the Owin middlware calling it normally… And neither SerializeTicket or DeserializeTicket throw an error…

So, you need to use Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer to searizize and deserialize ticket. It will be look like this:

Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer serializer                 = new Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer();  token.ProtectedTicket = System.Text.Encoding.Default.GetString(serializer.Serialize(context.Ticket)); 

instead of:

token.ProtectedTicket = context.SerializeTicket(); 

And for ReceiveAsync method:

Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer serializer = new Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer(); context.SetTicket(serializer.Deserialize(System.Text.Encoding.Default.GetBytes(refreshToken.ProtectedTicket))); 

instead of:

context.DeserializeTicket(refreshToken.ProtectedTicket); 

3) Now you need to add refresh_token to ExternalLogin method response. Override AuthorizationEndpointResponse in your OAuthAuthorizationServerProvider. Something like this:

public override Task AuthorizationEndpointResponse(OAuthAuthorizationEndpointResponseContext context) {      var refreshToken = context.OwinContext.Authentication.AuthenticationResponseGrant.Properties.Dictionary["refresh_token"];      if (!string.IsNullOrEmpty(refreshToken))      {           context.AdditionalResponseParameters.Add("refresh_token", refreshToken);      }      return base.AuthorizationEndpointResponse(context); } 

So.. thats all! Now, after calling ExternalLogin method, you get url: https://localhost:44301/Account/ExternalLoginCallback?access_token=ACCESS_TOKEN&token_type=bearer&expires_in=300&state=STATE&refresh_token=TICKET&returnUrl=URL

I hope this helps)

like image 92
Giraffe Avatar answered Nov 21 '22 13:11

Giraffe



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!