wake-up-neo.com

Verarbeiten abgelaufener Aktualisierungstoken in ASP.NET Core

Unten finden Sie Code, mit dem dieses Problem behoben wurde

Ich versuche, den besten und effizientesten Weg zu finden, um mit einem Aktualisierungstoken umzugehen, das in ASP.NET Core 2.1 abgelaufen ist.

Lassen Sie mich etwas mehr erklären.

Ich verwende OAUTH2 und OIDC, um Berechtigungscode-Erteilungsflüsse (oder Hybridfluss mit OIDC) anzufordern. Dieser Flow-/Grant-Typ ermöglicht mir den Zugriff auf ein AccessToken und ein RefreshToken (Autorisierungscode ebenfalls, dies gilt jedoch nicht für diese Frage).

Das Zugriffstoken und das Aktualisierungstoken werden vom ASP.NET-Kern gespeichert und können mit HttpContext.GetTokenAsync("access_token"); bzw. HttpContext.GetTokenAsync("refresh_token"); abgerufen werden.

Ich kann den access_token Ohne Probleme aktualisieren. Das Problem tritt ins Spiel, wenn der refresh_token Abgelaufen, widerrufen oder in irgendeiner Weise ungültig ist.

Der richtige Ablauf besteht darin, dass sich der Benutzer erneut anmeldet und den gesamten Authentifizierungsablauf erneut durchläuft. Dann erhält die Anwendung einen neuen Satz Token zurück.

Meine Frage ist, wie dies in der besten und korrektesten Methode erreicht werden kann. Ich habe beschlossen, eine benutzerdefinierte Middleware zu schreiben, die versucht, den access_token Zu erneuern, wenn er abgelaufen ist. Die Middleware setzt dann das neue Token in das AuthenticationProperties für den HTTP-Kontext, damit es später von allen Aufrufen in der Pipe verwendet werden kann.

Wenn die Aktualisierung des Tokens aus irgendeinem Grund fehlschlägt, muss ChallengeAsync erneut aufgerufen werden. Ich rufe ChallengeAsync von der Middleware aus auf.

Hier stoße ich auf ein interessantes Verhalten. Meistens funktioniert dies jedoch, manchmal erhalte ich 500 Fehler ohne hilfreiche Informationen darüber, was fehlschlägt. Es scheint fast so, als ob die Middleware Probleme hat, ChallengeAsync von der Middleware aus aufzurufen, und vielleicht versucht auch eine andere Middleware, auf den Kontext zuzugreifen.

Ich bin nicht ganz sicher, was los ist. Ich bin mir nicht ganz sicher, ob dies der richtige Ort ist, um diese Logik umzusetzen oder nicht. Vielleicht sollte ich das nicht in der Middleware haben, vielleicht woanders. Vielleicht ist Polly für den HTTP-Client der beste Ort.

Ich bin offen für alle Ideen.

Vielen Dank für jede Hilfe, die Sie zur Verfügung stellen können.

Codelösung, die bei mir funktioniert hat


Vielen Dank an Mickaël Derriey für die Hilfe und Anleitung (siehe seine Antwort für weitere Informationen zum Kontext dieser Lösung). Dies ist die Lösung, die ich mir ausgedacht habe und die für mich funktioniert:

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

Das Zugriffstoken und das Aktualisierungstoken werden vom ASP.NET-Kern gespeichert

Ich denke, es ist wichtig zu beachten, dass die Token in dem Cookie gespeichert werden, das den Benutzer für Ihre Anwendung identifiziert.

Dies ist meine Meinung, aber ich glaube nicht, dass eine benutzerdefinierte Middleware der richtige Ort ist, um Token zu aktualisieren. Der Grund dafür ist, dass Sie, wenn Sie das Token erfolgreich aktualisieren, das vorhandene Token ersetzen und es in Form eines neuen Cookies, das das vorhandene ersetzt, an den Browser zurücksenden müssen.

Aus diesem Grund denke ich, ist der relevanteste Ort dafür, wenn das Cookie von ASP.NET Core gelesen wird. Jeder Authentifizierungsmechanismus legt mehrere Ereignisse offen. Für Cookies gibt es eine mit dem Namen ValidatePrincipal, die bei jeder Anforderung aufgerufen wird, nachdem das Cookie gelesen und eine Identität daraus erfolgreich deserialisiert wurde.

public void ConfigureServices(ServiceCollection services)
{
    services
        .AddAuthentication()
        .AddCookies(new CookieAuthenticationOptions
        {
            Events = new CookieAuthenticationEvents
            {
                OnValidatePrincipal = context =>
                {
                    // context.Principal gives you access to the logged-in user
                    // context.Properties.GetTokens() gives you access to all the tokens

                    return Task.CompletedTask;
                }
            }
        });
}

Das Schöne an diesem Ansatz ist, dass, wenn Sie es schaffen, das Token zu erneuern und in AuthenticationProperties zu speichern, die Variable context vom Typ CookieValidatePrincipalContext eine Eigenschaft namens hat ShouldRenew . Wenn Sie diese Eigenschaft auf true setzen, wird die Middleware angewiesen, ein neues Cookie auszustellen.

Wenn Sie das Token nicht erneuern können oder feststellen, dass das Aktualisierungstoken abgelaufen ist und Sie verhindern möchten, dass der Benutzer fortfährt, verfügt dieselbe Klasse über eine RejectPrincipal -Methode, die das Cookie anweist Middleware, um die Anfrage als anonym zu behandeln.

Das Schöne daran ist, dass wenn Ihre MVC-App nur authentifizierten Benutzern den Zugriff darauf ermöglicht, MVC die Antwort HTTP 401 Ausgibt, die das Authentifizierungssystem auffängt und in eine Challenge umwandelt, und der Benutzer umgeleitet wird zurück zum Identity Provider.

Ich habe einen Code, der zeigt, wie dies im mderriey/TokenRenewal -Repository auf GitHub funktionieren würde. Während die Absicht anders ist, zeigt es die Mechanik der Verwendung dieser Ereignisse.

10