Segurança OWIN – Como implementar tokens de atualização do OAuth2

Eu estou usando o modelo Web Api 2 que vem com o Visual Studio 2013 tem alguns middleware OWIN para fazer a autenticação do usuário e os gostos de.

No OAuthAuthorizationServerOptions , notei que o servidor OAuth2 está configurado para distribuir tokens que expiram em 14 dias

  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 }; 

Isso não é adequado para meu último projeto. Eu gostaria de distribuir bearer_tokens curtos que podem ser atualizados usando um refresh_token

Eu fiz muitos googling e não consigo encontrar nada de útil.

Então, isso é o quão longe eu consegui chegar. Eu agora cheguei ao ponto de “WTF eu agora”.

Eu escrevi um RefreshTokenProvider que implementa IAuthenticationTokenProvider conforme a propriedade OAuthAuthorizationServerOptions na class 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 }; 

Então, agora, quando alguém solicita um bearer_token , estou enviando um refresh_token , o que é ótimo.

Então, como eu uso este refresh_token para obter um novo bearer_token , presumivelmente eu preciso enviar um pedido para o meu endpoint de token com algum conjunto específico de HTTP Headers?

Apenas pensando em voz alta enquanto digito … Devo manusear a expiração SimpleRefreshTokenProvider no meu SimpleRefreshTokenProvider ? Como um cliente obteria um novo refresh_token ?

Eu realmente poderia fazer com algum material de leitura / documentação, porque eu não quero entender isso errado e gostaria de seguir algum tipo de padrão.

Acabei de implementar meu serviço OWIN com portador (chamado access_token no seguinte) e atualizar tokens. Minha percepção disso é que você pode usar streams diferentes. Portanto, depende do stream que você deseja usar como definir seus tempos de expiração access_token e refresh_token.

Vou descrever dois streams A e B no seguinte (sugiro o que você quer que seja o stream B):

A) o tempo de expiração de access_token e refresh_token é o mesmo que por padrão, 1200 segundos ou 20 minutos. Esse stream precisa primeiro de seu cliente para enviar client_id e client_secret com dados de login para obter um access_token, refresh_token e expiration_time. Com o refresh_token, agora é possível obter um novo access_token por 20 minutos (ou o que quer que você defina como AccessTokenExpireTimeSpan no OAuthAuthorizationServerOptions). Pelo motivo que o tempo de expiração de access_token e refresh_token é o mesmo, seu cliente é responsável por obter um novo access_token antes do tempo de expiração! Por exemplo, seu cliente poderia enviar uma chamada POST de atualização para seu ponto de extremidade de token com o corpo (observação: você deve usar https na produção)

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

para obter um novo token após, por exemplo, 19 minutos para evitar que os tokens expirem.

B) nesse stream, você deseja ter uma expiração de curto prazo para seu access_token e uma expiração de longo prazo para seu refresh_token. Vamos supor que para o propósito de teste você configure o access_token para expirar em 10 segundos ( AccessTokenExpireTimeSpan = TimeSpan.FromSeconds(10) ) e o refresh_token para 5 Minutos. Agora vem a parte interessante de definir o tempo de expiração de refresh_token: Você faz isso em sua function createAsync na class SimpleRefreshTokenProvider como esta:

 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); 

Agora, seu cliente pode enviar uma chamada POST com um refresh_token para seu terminal de token quando o access_token estiver expirado. A parte do corpo da chamada pode ter esta grant_type=refresh_token&client_id=xxxxxx&refresh_token=xxxxxxxx-xxxx-xxxx-xxxx-xx : grant_type=refresh_token&client_id=xxxxxx&refresh_token=xxxxxxxx-xxxx-xxxx-xxxx-xx

Uma coisa importante é que você pode querer usar este código não apenas em sua function CreateAsync, mas também em sua function Create. Portanto, você deve considerar usar sua própria function (por exemplo, chamada CreateTokenInternal) para o código acima. Aqui você pode encontrar implementações de diferentes streams incluindo o stream refresh_token (mas sem definir o tempo de expiração do refresh_token)

Aqui está uma implementação de exemplo de IAuthenticationTokenProvider no github (com a configuração do tempo de expiração do refresh_token)

Lamentamos não poder ajudar com mais materiais do que as especificações do OAuth e a documentação da API da Microsoft. Eu colocaria os links aqui, mas minha reputação não me deixa postar mais de 2 links ….

Espero que isso possa ajudar alguns outros a pouparem tempo ao tentar implementar o OAuth2.0 com o tempo de expiração refresh_token diferente do tempo de expiração access_token. Não consegui encontrar um exemplo de implementação na web (exceto o de thinktecture linkado acima) e levei algumas horas de investigação até que funcionou para mim.

Nova informação: No meu caso, tenho duas possibilidades diferentes para receber tokens. Uma delas é receber um access_token válido. Lá eu tenho que enviar uma chamada POST com um corpo String em formato application / x-www-form-urlencoded com os seguintes dados

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

Segundo, se access_token não for mais válido, podemos tentar o refresh_token enviando uma chamada POST com um corpo String em formato application/x-www-form-urlencoded com os seguintes dados: grant_type=refresh_token&client_id=YOURCLIENTID&refresh_token=YOURREFRESHTOKENGUID

Você precisa implementar RefreshTokenProvider . Primeiro crie class para RefreshTokenProvider ie.

 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); } } 

Em seguida, adicione a instância ao OAuthOptions .

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

Eu não acho que você deveria estar usando uma matriz para manter os tokens. Nem você precisa de um guia como um símbolo.

Você pode facilmente usar context.SerializeTicket ().

Veja meu código abaixo.

 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); } } 

A resposta de Freddy me ajudou muito a fazer isso funcionar. Por uma questão de completude, aqui está como você pode implementar o hash do token:

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

Em 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); } } }