Manejar tokens de actualización caducados en ASP.NET Core

VER A continuación el código que resolvió este problema

Estoy tratando de encontrar la mejor y más eficiente manera de lidiar con un token de actualización que ha caducado dentro de ASP.NET Core 2.1.

Déjame explicarte un poco más.

Estoy usando OAUTH2 y OIDC para solicitar flujos de concesión del Código de autorización (o flujo híbrido con OIDC). Este tipo de flujo / concesión me da acceso a un AccessToken y un RefreshToken (código de autorización también, pero eso no es para esta pregunta).

El token de acceso y el token de actualización son almacenados por ASP.NET core, y pueden recuperarse usandoHttpContext.GetTokenAsync("access_token"); yHttpContext.GetTokenAsync("refresh_token"); respectivamente.

Puedo actualizar elaccess_token sin ningún problema. El problema entra en juego cuando elrefresh_token ha caducado, revocado o no es válido de alguna manera.

El flujo correcto sería hacer que el usuario inicie sesión nuevamente y regrese a través de todo el flujo de autenticación nuevamente. Luego, la aplicación obtiene un nuevo conjunto de tokens devueltos.

Mi pregunta es cómo lograr esto con el método mejor y más correcto. Decidí escribir un middleware personalizado que intente renovar elaccess_token si ha expirado. El middleware luego establece el nuevo token enAuthenticationProperties para el HttpContext para que pueda ser utilizado por cualquier llamada posterio

Si la actualización del token falla por algún motivo, debo volver a llamar a ChallengeAsync. Llamo a ChallengeAsync desde el middleware.

Aquí es donde me encuentro con un comportamiento interesante. Sin embargo, la mayoría de las veces esto funciona, a veces recibo 500 errores sin información útil sobre lo que está fallando. Casi parece que el middleware está teniendo problemas para intentar llamar a ChallengeAsync desde el middleware, y tal vez otro middleware también está intentando acceder al contexto.

No estoy muy seguro de lo que está pasando. No estoy muy seguro de si este es el lugar correcto para poner esta lógica o no. Tal vez no debería tener esto en el middleware, tal vez en otro lugar. Quizás Polly para el HttpClient es el mejor lugar.

Estoy abierto a cualquier idea.

Gracias por cualquier ayuda que usted nos pueda proporcionar

Code Solución que funcionó para mí

Gracias a Mickaël Derriey para obtener ayuda y dirección (asegúrese de ver su respuesta para obtener más información sobre el contexto de esta resolución). Esta es la solución que he encontrado y funciona para mí:

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

Respuestas a la pregunta(1)

Su respuesta a la pregunta