Manipulando Tokens de Atualização Expirados no ASP.NET Core
VEJA abaixo o código que resolveu esse problema
Estou tentando encontrar a melhor e mais eficiente maneira de lidar com um token de atualização que expirou no ASP.NET Core 2.1.
Deixe-me explicar um pouco mais.
Estou usando OAUTH2 e OIDC para solicitar fluxos de concessão de código de autorização (ou fluxo híbrido com OIDC). Esse tipo de fluxo / concessão me dá acesso a um AccessToken e a RefreshToken (código de autorização também, mas não é para esta pergunta).
O token de acesso e o token de atualização são armazenados pelo núcleo do ASP.NET e podem ser recuperados usandoHttpContext.GetTokenAsync("access_token");
eHttpContext.GetTokenAsync("refresh_token");
respectivamente.
Eu posso atualizar oaccess_token
sem problemas. A questão entra em jogo quando orefresh_token
expirou, revogado ou inválido de alguma forma.
O fluxo correto seria fazer com que o usuário efetue login novamente e volte através de todo o fluxo de autenticação novamente. Em seguida, o aplicativo obtém um novo conjunto de tokens retornados.
Minha pergunta é como conseguir isso do melhor e mais correto método. Decidi escrever um middleware personalizado que tenta renovar oaccess_token
se expirou. O middleware define o novo token no diretórioAuthenticationProperties
para o HttpContext, para que possa ser usado por todas as chamadas mais tarde no canal.
Se a atualização do token falhar por qualquer motivo, preciso chamar ChallengeAsync novamente. Estou chamando ChallengeAsync a partir do middleware.
É aqui que estou tendo um comportamento interessante. Porém, na maioria das vezes isso funciona, algumas vezes recebo 500 erros sem informações úteis sobre o que está falhando. Quase parece que o middleware está tendo problemas ao tentar chamar ChallengeAsync a partir do middleware, e talvez outro middleware também esteja tentando acessar o contexto.
Não tenho muita certeza do que está acontecendo. Não tenho certeza se este é o lugar certo para colocar essa lógica ou não. Talvez eu não deva ter isso no middleware, talvez em outro lugar. Talvez Polly para o HttpClient seja o melhor lugar.
Estou aberto a quaisquer idéias.
Obrigado por qualquer ajuda que você pode fornecer.
Solução de código que funcionou para mim
Graças aMickaël Derriey pela ajuda e orientação (não deixe de ver a resposta dele para obter mais informações sobre o contexto desta solução) Esta é a solução que eu criei e funcionou para mim:
options.Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = context =>
{
//check to see if user is authenticated first
if (context.Principal.Identity.IsAuthenticated)
{
//get the users tokens
var tokens = context.Properties.GetTokens();
var refreshToken = tokens.FirstOrDefault(t => t.Name == "refresh_token");
var accessToken = tokens.FirstOrDefault(t => t.Name == "access_token");
var exp = tokens.FirstOrDefault(t => t.Name == "expires_at");
var expires = DateTime.Parse(exp.Value);
//check to see if the token has expired
if (expires < DateTime.Now)
{
//token is expired, let's attempt to renew
var tokenEndpoint = "https://token.endpoint.server";
var tokenClient = new TokenClient(tokenEndpoint, clientId, clientSecret);
var tokenResponse = tokenClient.RequestRefreshTokenAsync(refreshToken.Value).Result;
//check for error while renewing - any error will trigger a new login.
if (tokenResponse.IsError)
{
//reject Principal
context.RejectPrincipal();
return Task.CompletedTask;
}
//set new token values
refreshToken.Value = tokenResponse.RefreshToken;
accessToken.Value = tokenResponse.AccessToken;
//set new expiration date
var newExpires = DateTime.UtcNow + TimeSpan.FromSeconds(tokenResponse.ExpiresIn);
exp.Value = newExpires.ToString("o", CultureInfo.InvariantCulture);
//set tokens in auth properties
context.Properties.StoreTokens(tokens);
//trigger context to renew cookie with new token values
context.ShouldRenew = true;
return Task.CompletedTask;
}
}
return Task.CompletedTask;
}
};