OWIN Security – Cómo implementar los tokens de actualización de OAuth2

Estoy usando la plantilla Web Api 2 que viene con Visual Studio 2013 tiene algunos middleware OWIN para hacer Autenticación de usuario y me gusta.

En OAuthAuthorizationServerOptions noté que el servidor OAuth2 está configurado para distribuir tokens que caducan en 14 días

  OAuthOptions = new OAuthAuthorizationServerOptions { TokenEndpointPath = new PathString("/api/token"), Provider = new ApplicationOAuthProvider(PublicClientId,UserManagerFactory) , AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"), AccessTokenExpireTimeSpan = TimeSpan.FromDays(14), AllowInsecureHttp = true }; 

Esto no es adecuado para mi último proyecto. Me gustaría repartir bearer_tokens de corta duración que se puedan actualizar con un refresh_token

He hecho muchas búsquedas en Google y no encuentro nada útil.

Así que esto es lo lejos que he logrado llegar. Ahora llegué al punto de “WTF do I now”.

He escrito un RefreshTokenProvider que implementa IAuthenticationTokenProvider según la propiedad OAuthAuthorizationServerOptions clase OAuthAuthorizationServerOptions :

  public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider { private static ConcurrentDictionary _refreshTokens = new ConcurrentDictionary(); public async Task CreateAsync(AuthenticationTokenCreateContext context) { var guid = Guid.NewGuid().ToString(); _refreshTokens.TryAdd(guid, context.Ticket); // hash?? context.SetToken(guid); } public async Task ReceiveAsync(AuthenticationTokenReceiveContext context) { AuthenticationTicket ticket; if (_refreshTokens.TryRemove(context.Token, out ticket)) { context.SetTicket(ticket); } } public void Create(AuthenticationTokenCreateContext context) { throw new NotImplementedException(); } public void Receive(AuthenticationTokenReceiveContext context) { throw new NotImplementedException(); } } // Now in my Startup.Auth.cs OAuthOptions = new OAuthAuthorizationServerOptions { TokenEndpointPath = new PathString("/api/token"), Provider = new ApplicationOAuthProvider(PublicClientId,UserManagerFactory) , AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"), AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(2), AllowInsecureHttp = true, RefreshTokenProvider = new RefreshTokenProvider() // This is my test }; 

Entonces, cuando alguien solicita un bearer_token ahora estoy enviando un refresh_token , que es genial.

Entonces, ¿cómo utilizo este refresh_token para obtener un nuevo bearer_token , presumiblemente necesito enviar una solicitud a mi token endpoint con un determinado conjunto de encabezados HTTP?

Solo pienso en voz alta mientras escribo … ¿Debo manejar la caducidad de refresh_token en mi SimpleRefreshTokenProvider ? ¿Cómo obtendría un cliente un nuevo refresh_token ?

Realmente podría hacer algo de material de lectura / documentación porque no quiero equivocarme y me gustaría seguir algún tipo de estándar.

Acabo de implementar mi servicio OWIN con portador (llamado access_token en lo sucesivo) y Actualizar tokens. Mi percepción de esto es que puedes usar diferentes flujos. Por lo tanto, depende del flujo que desee usar para configurar los tiempos de vencimiento de access_token y refresh_token.

Describiré dos flujos A y B en el siguiente paso (sugiero que lo que quieres es flujo B):

A) el tiempo de expiración de access_token y refresh_token es el mismo que el predeterminado 1200 segundos o 20 minutos. Este flujo necesita que su cliente primero envíe client_id y client_secret con datos de inicio de sesión para obtener un access_token, refresh_token y expiration_time. Con refresh_token, ahora es posible obtener un nuevo access_token durante 20 minutos (o lo que sea que configure AccessTokenExpireTimeSpan en OAuthAuthorizationServerOptions en). Debido a que el tiempo de caducidad de access_token y refresh_token es el mismo, ¡su cliente es responsable de obtener un nuevo access_token antes de la fecha de caducidad! Por ejemplo, su cliente podría enviar una actualización de la llamada POST a su punto final del token con el cuerpo (observación: debe usar https en producción)

 grant_type=refresh_token&client_id=xxxxxx&refresh_token=xxxxxxxx-xxxx-xxxx-xxxx-xxxxx 

para obtener un nuevo token después de, por ejemplo, 19 minutos para evitar que los tokens caduquen.

B) en este flujo, desea tener un vencimiento a corto plazo para su access_token y una caducidad a largo plazo para su refresh_token. Supongamos para fines de prueba que configura access_token para que caduque en 10 segundos ( AccessTokenExpireTimeSpan = TimeSpan.FromSeconds(10) ) y refresh_token en 5 minutos. Ahora se trata de la parte interesante de configurar el tiempo de expiración de refresh_token: esto se hace en la función createAsync en la clase SimpleRefreshTokenProvider de esta manera:

 var guid = Guid.NewGuid().ToString(); //copy properties and set the desired lifetime of refresh token var refreshTokenProperties = new AuthenticationProperties(context.Ticket.Properties.Dictionary) { IssuedUtc = context.Ticket.Properties.IssuedUtc, ExpiresUtc = DateTime.UtcNow.AddMinutes(5) //SET DATETIME to 5 Minutes //ExpiresUtc = DateTime.UtcNow.AddMonths(3) }; /*CREATE A NEW TICKET WITH EXPIRATION TIME OF 5 MINUTES *INCLUDING THE VALUES OF THE CONTEXT TICKET: SO ALL WE *DO HERE IS TO ADD THE PROPERTIES IssuedUtc and *ExpiredUtc to the TICKET*/ var refreshTokenTicket = new AuthenticationTicket(context.Ticket.Identity, refreshTokenProperties); //saving the new refreshTokenTicket to a local var of Type ConcurrentDictionary // consider storing only the hash of the handle RefreshTokens.TryAdd(guid, refreshTokenTicket); context.SetToken(guid); 

Ahora su cliente puede enviar una llamada POST con una refresh_token a su punto final token cuando access_token . La parte del cuerpo de la llamada puede verse así: grant_type=refresh_token&client_id=xxxxxx&refresh_token=xxxxxxxx-xxxx-xxxx-xxxx-xx

Una cosa importante es que quizás desee utilizar este código no solo en su función CreateAsync sino también en su función Create. Por lo tanto, debe considerar usar su propia función (por ejemplo, llamada CreateTokenInternal) para el código anterior. Aquí puede encontrar implementaciones de diferentes flujos, incluido refresh_token flow (pero sin establecer el tiempo de caducidad de refresh_token)

Aquí hay una implementación de muestra de IAuthenticationTokenProvider en github (con la configuración del tiempo de caducidad de refresh_token)

Lamento que no pueda ayudar con más materiales que las especificaciones de OAuth y la documentación de la API de Microsoft. Publicaba los enlaces aquí, pero mi reputación no me permite publicar más de 2 enlaces …

Espero que esto ayude a otros a ahorrar tiempo cuando intenten implementar OAuth2.0 con refresh_token tiempo de caducidad diferente a access_token expiration time. No pude encontrar un ejemplo de implementación en la web (excepto el de thinktecture vinculado anteriormente) y me llevó algunas horas de investigación hasta que funcionó para mí.

Nueva información: en mi caso tengo dos posibilidades diferentes para recibir tokens. Una es recibir un access_token válido. Allí tengo que enviar una llamada POST con un cuerpo de cadena en formato application / x-www-form-urlencoded con los siguientes datos

 client_id=YOURCLIENTID&grant_type=password&username=YOURUSERNAME&password=YOURPASSWORD 

Segundo, si access_token ya no es válido, podemos probar el refresh_token enviando una llamada POST con un cuerpo String en formato application/x-www-form-urlencoded con los siguientes datos grant_type=refresh_token&client_id=YOURCLIENTID&refresh_token=YOURREFRESHTOKENGUID

Debe implementar RefreshTokenProvider . Primero crea una clase para RefreshTokenProvider, es decir.

 public class ApplicationRefreshTokenProvider : AuthenticationTokenProvider { public override void Create(AuthenticationTokenCreateContext context) { // Expiration time in seconds int expire = 5*60; context.Ticket.Properties.ExpiresUtc = new DateTimeOffset(DateTime.Now.AddSeconds(expire)); context.SetToken(context.SerializeTicket()); } public override void Receive(AuthenticationTokenReceiveContext context) { context.DeserializeTicket(context.Token); } } 

A continuación, agregue instancia a OAuthOptions .

 OAuthOptions = new OAuthAuthorizationServerOptions { TokenEndpointPath = new PathString("/authenticate"), Provider = new ApplicationOAuthProvider(), AccessTokenExpireTimeSpan = TimeSpan.FromSeconds(expire), RefreshTokenProvider = new ApplicationRefreshTokenProvider() }; 

No creo que deba usar una matriz para mantener los tokens. Ni necesitas una guía como token.

Puede usar context.SerializeTicket () fácilmente.

Ver mi código a continuación.

 public class RefreshTokenProvider : IAuthenticationTokenProvider { public async Task CreateAsync(AuthenticationTokenCreateContext context) { Create(context); } public async Task ReceiveAsync(AuthenticationTokenReceiveContext context) { Receive(context); } public void Create(AuthenticationTokenCreateContext context) { object inputs; context.OwinContext.Environment.TryGetValue("Microsoft.Owin.Form#collection", out inputs); var grantType = ((FormCollection)inputs)?.GetValues("grant_type"); var grant = grantType.FirstOrDefault(); if (grant == null || grant.Equals("refresh_token")) return; context.Ticket.Properties.ExpiresUtc = DateTime.UtcNow.AddDays(Constants.RefreshTokenExpiryInDays); context.SetToken(context.SerializeTicket()); } public void Receive(AuthenticationTokenReceiveContext context) { context.DeserializeTicket(context.Token); if (context.Ticket == null) { context.Response.StatusCode = 400; context.Response.ContentType = "application/json"; context.Response.ReasonPhrase = "invalid token"; return; } if (context.Ticket.Properties.ExpiresUtc <= DateTime.UtcNow) { context.Response.StatusCode = 401; context.Response.ContentType = "application/json"; context.Response.ReasonPhrase = "unauthorized"; return; } context.Ticket.Properties.ExpiresUtc = DateTime.UtcNow.AddDays(Constants.RefreshTokenExpiryInDays); context.SetTicket(context.Ticket); } } 

La respuesta de Freddy me ayudó mucho para que esto funcionara. En aras de la exhaustividad, aquí se explica cómo podría implementar el hashing del token:

 private string ComputeHash(Guid input) { byte[] source = input.ToByteArray(); var encoder = new SHA256Managed(); byte[] encoded = encoder.ComputeHash(source); return Convert.ToBase64String(encoded); } 

En CreateAsync :

 var guid = Guid.NewGuid(); ... _refreshTokens.TryAdd(ComputeHash(guid), refreshTokenTicket); context.SetToken(guid.ToString()); 

ReceiveAsync :

 public async Task ReceiveAsync(AuthenticationTokenReceiveContext context) { Guid token; if (Guid.TryParse(context.Token, out token)) { AuthenticationTicket ticket; if (_refreshTokens.TryRemove(ComputeHash(token), out ticket)) { context.SetTicket(ticket); } } }