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

questionAnswers(1)

yourAnswerToTheQuestion