Estoy usando la plantilla Web Api 2 que viene con Visual Studio 2013 y tiene algún middleware OWIN para realizar la autenticación de usuario y similares.
En el 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 entregar bearer_tokens de corta duración que se pueden actualizar usando unrefresh_token
He hecho muchas búsquedas en Google y no encuentro nada útil.
Así que esto es lo lejos que he logrado llegar. Ahora he llegado al punto de "WTF do I now".
He escrito un RefreshTokenProvider
que se implementa IAuthenticationTokenProvider
según la RefreshTokenProvider
propiedad en la OAuthAuthorizationServerOptions
clase:
public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider
{
private static ConcurrentDictionary<string, AuthenticationTicket> _refreshTokens = new ConcurrentDictionary<string, AuthenticationTicket>();
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
, lo cual es genial.
Entonces, ¿cómo uso este refresh_token para obtener un nuevo bearer_token
, presumiblemente necesito enviar una solicitud a mi punto final de token con algunos encabezados HTTP específicos configurados?
Solo pienso en voz alta mientras escribo ... ¿Debo manejar la expiración de refresh_token en mi SimpleRefreshTokenProvider
? ¿Cómo obtendría un cliente uno nuevo refresh_token
?
Realmente me vendría bien algún material de lectura / documentación porque no quiero equivocarme y me gustaría seguir algún tipo de estándar.
fuente
Respuestas:
Acabo de implementar mi servicio OWIN con Bearer (llamado access_token a continuación) y Refresh Tokens. Mi idea de esto es que puede utilizar diferentes flujos. Por lo tanto, depende del flujo que desee usar cómo configure sus tiempos de vencimiento de access_token y refresh_token.
Describiré dos flujos A y B a continuación (sugiero que lo que desea tener es el flujo B):
A) el tiempo de vencimiento de access_token y refresh_token es el mismo que por defecto 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 establezca AccessTokenExpireTimeSpan en OAuthAuthorizationServerOptions). Debido a que el tiempo de vencimiento de access_token y refresh_token son los mismos, su cliente es responsable de obtener un nuevo access_token antes del tiempo de vencimiento. Por ejemplo, su cliente podría enviar una llamada POST de actualización a su punto final de token con el cuerpo (observación: debe usar https en producción)
para obtener un nuevo token después de, por ejemplo, 19 minutos para evitar que caduquen.
B) en este flujo, desea tener un vencimiento a corto plazo para su access_token y un vencimiento a largo plazo para su refresh_token. Supongamos, con fines de prueba, que configuró el access_token para que expire en 10 segundos (
AccessTokenExpireTimeSpan = TimeSpan.FromSeconds(10)
) y el refresh_token en 5 minutos. Ahora se trata de la parte interesante que establece el tiempo de vencimiento de refresh_token: haz esto en tu 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<string,AuthenticationTicket> // consider storing only the hash of the handle RefreshTokens.TryAdd(guid, refreshTokenTicket); context.SetToken(guid);
Ahora su cliente puede enviar una llamada POST con un refresh_token a su punto final de token cuando
access_token
expire. 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 es posible que desee utilizar este código no solo en su función CreateAsync sino también en su función Create. Por lo tanto, debería considerar utilizar su propia función (por ejemplo, llamada CreateTokenInternal) para el código anterior. Aquí puede encontrar implementaciones de diferentes flujos, incluido el flujo de refresh_token (pero sin establecer el tiempo de vencimiento del refresh_token)
Aquí hay una implementación de muestra de IAuthenticationTokenProvider en github (con la configuración del tiempo de vencimiento del refresh_token)
Lamento no poder ayudar con más material que las especificaciones de OAuth y la documentación de la API de Microsoft. Publicaría los enlaces aquí, pero mi reputación no me permite publicar más de 2 enlaces ...
Espero que esto pueda ayudar a otros a ahorrar tiempo al intentar implementar OAuth2.0 con un tiempo de expiración de refresh_token diferente al tiempo de expiración de access_token. No pude encontrar una implementación de ejemplo en la web (excepto la de thinktecture vinculada anteriormente) y me tomó algunas horas de investigación hasta que funcionó para mí.
Nueva información: En mi caso tengo dos posibilidades diferentes para recibir tokens. Uno 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
En segundo lugar, si el 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 datosgrant_type=refresh_token&client_id=YOURCLIENTID&refresh_token=YOURREFRESHTOKENGUID
fuente
RefreshTokens
, por lo que siRefreshTokens
se filtra, ¿¡un atacante no puede usar esa información !?Necesita implementar RefreshTokenProvider . Primero cree la 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); } }
Luego, agregue la instancia a OAuthOptions .
OAuthOptions = new OAuthAuthorizationServerOptions { TokenEndpointPath = new PathString("/authenticate"), Provider = new ApplicationOAuthProvider(), AccessTokenExpireTimeSpan = TimeSpan.FromSeconds(expire), RefreshTokenProvider = new ApplicationRefreshTokenProvider() };
fuente
context.OwinContext.Environment
contiene unaMicrosoft.Owin.Form#collection
clave que le da unaFormCollection
donde se puede encontrar el tipo de subvención y añadir una ficha en consecuencia. Se está filtrando la implementación, puede fallar en cualquier momento con actualizaciones futuras y no estoy seguro de si es portátil entre hosts OWIN.var form = await context.Request.ReadFormAsync();
var grantType = form.GetValue("grant_type");
luego emita el token de actualización si el tipo de concesión no es "refresh_token"No creo que debas usar una matriz para mantener tokens. Tampoco necesitas una guía como token.
Puede usar context.SerializeTicket () fácilmente.
Vea 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); } }
fuente
La respuesta de Freddy me ayudó mucho a que esto funcionara. En aras de la integridad, así es como puede implementar el hash 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); } } }
fuente