wake-up-neo.com

Aktualisieren von Zugriffstoken in IdentityServer4-Clients

Ich frage mich, wie ein Zugriffstoken in einem IdentityServer4-Client mithilfe des Hybridflusses aktualisiert werden kann, der mit ASP.NET Core MVC erstellt wurde.

Wenn ich das gesamte Konzept richtig verstanden habe, muss der Client zuerst über den Bereich "offline_access" verfügen, um Aktualisierungstoken verwenden zu können. Dies ist eine bewährte Methode, um kurzlebige Zugriffstoken zu aktivieren und Aktualisierungstoken zu widerrufen, die verhindern, dass neue Zugriffstoken verwendet werden ausgestellt werden.

Ich habe erfolgreich ein Zugriffstoken und ein Aktualisierungstoken erhalten, aber wie soll ich mit dem eigentlichen Aktualisierungsvorgang des Zugriffstokens im MVC-Client umgehen?

Kann die OpenId Connect (OIDC) Middleware dies automatisch erledigen? Oder sollte ich die Ablaufzeit des Zugriffstokens lieber überall dort überprüfen, wo ich WEB-APIs aufrufe, indem ich grundsätzlich überprüfe, ob das Zugriffstoken abgelaufen ist oder sehr bald abläuft (bevorstehende 30 Sekunden), dann das Zugriffstoken durch Aufrufen des Token-Endpunkts mit dem Aktualisierungstoken aktualisieren ?

Wird empfohlen, die Erweiterungsmethode IdentityModel2 library TokenClientRequestRefreshTokenAsync in meinen Controller-Aktionsmethoden zum Aufrufen des Token-Endpunkts zu verwenden?

Ich habe Code gesehen, der in den OIDC-Middleware-Ereignissen Zugriffstoken anfordert und mithilfe des Antwortspeichers einen Anspruch erhebt, der ein Ablaufdatum enthält. Das Problem ist, dass mein OIDC in gewisser Weise bereits automatisch einen Zugriffstoken anfordert, so dass es kein gutes Gefühl ist, einen neuen Zugriffstoken direkt nach Erhalt des ersten anzufordern.

Beispiel einer Controller-Aktionsmethode ohne Aktualisierungslogik für Zugriffstoken:

public async Task<IActionResult> GetInvoices()
    {
        var token = await HttpContext.Authentication.GetTokenAsync("access_token");

        var client = new HttpClient();
        client.SetBearerToken(token);

        var response = await client.GetStringAsync("http://localhost:5001/api/getInvoices");
        ViewBag.Json = JArray.Parse(response).ToString();

        return View();
    }
9
Jonas

Die OIDC-Middleware übernimmt dies nicht für Sie. Sie wird ausgeführt, wenn eine HTTP 401-Antwort erkannt wird, und leitet den Benutzer dann zur IdentityServer-Anmeldeseite weiter. Nach der Weiterleitung zu Ihrer MVC-Anwendung werden Ansprüche in eine ClaimsIdentity umgewandelt und an die Cookies-Middleware weitergeleitet, die dies in einen Sitzungscookie umwandelt.

Bei jeder anderen Anforderung wird die OIDC-Middleware nicht berücksichtigt, solange der Cookie noch gültig ist.

Sie müssen sich also selbst darum kümmern. Sie sollten auch berücksichtigen, dass Sie das vorhandene Token aktualisieren müssen, wenn Sie das Zugriffstoken aktualisieren möchten, damit es nicht verloren geht. Wenn Sie dies nicht tun, enthält das Sitzungscookie immer das gleiche Token - das Original - und Sie werden es jedes Mal aktualisieren.

Eine Lösung, die ich gefunden habe, ist das Einbinden in die Cookies-Middleware. Hier ist der allgemeine Ablauf:

  • Verwenden Sie bei jeder Anforderung die Cookies-Middleware-Ereignisse, um das Zugriffstoken zu überprüfen
  • Wenn die Ablaufzeit fast erreicht ist, fordern Sie eine neue an
  • Ersetzen Sie die neuen Zugriffs- und Aktualisierungstoken in der ClaimsIdentity
  • Weisen Sie die Cookies-Middleware an, das Sitzungscookie so zu erneuern, dass es die neuen Token enthält

Bei diesem Ansatz gefällt mir, dass Sie in Ihrem MVC-Code so gut wie immer ein gültiges Zugriffstoken haben, es sei denn, die Referenzierung des Tokens schlägt mehrmals hintereinander fehl.

Was ich nicht mag, ist, dass es sehr an MVC gebunden ist - genauer gesagt an die Cookies-Middleware -, so dass es nicht wirklich portabel ist.

Sie können einen Blick auf dieses GitHub-Repo werfen, das ich zusammengestellt habe. In der Tat wird IdentityModel verwendet, da dies alles erledigt und den größten Teil der Komplexität der HTTP-Aufrufe an IdentityServer verbirgt.

11

Ich habe eine Lösung basierend auf einem Aktionsfilter zusammen mit der OIDC-Middleware in ASP.NET Core 2.0 erstellt.

AJAX-Anforderungen werden auch über den Aktionsfilter gesendet, wodurch das Zugriffstoken/Aktualisierungstoken aktualisiert wird.

https://Gist.github.com/devJ0n/43c6888161169e09fec542d2dc12af09

1
Jonas

Ich habe zwei mögliche Lösungen gefunden, die beide gleich sind, aber zu unterschiedlichen Zeiten in der OIDC-Middleware auftreten. In den Ereignissen extrahiere ich den Ablaufzeitwert des Zugriffstokens und speichere ihn als Anspruch, der später verwendet werden kann, um zu überprüfen, ob es in Ordnung ist, eine Web-API mit dem aktuellen Zugriffstoken aufzurufen, oder ob ich mithilfe der Aktualisierung einen neuen Zugriffstoken anfordern soll Zeichen.

Ich würde mich freuen, wenn jemand etwas dazu sagen könnte, welches dieser Ereignisse vorzuziehen ist.

var oidcOptions = new OpenIdConnectOptions
{
      AuthenticationScheme = appSettings.OpenIdConnect.AuthenticationScheme,
      SignInScheme = appSettings.OpenIdConnect.SignInScheme,

      Authority = appSettings.OpenIdConnect.Authority,
      RequireHttpsMetadata = _hostingEnvironment.IsDevelopment() ? false : true,
      PostLogoutRedirectUri = appSettings.OpenIdConnect.PostLogoutRedirectUri,

      ClientId = appSettings.OpenIdConnect.ClientId,
      ClientSecret = appSettings.OpenIdConnect.ClientSecret,
      ResponseType = appSettings.OpenIdConnect.ResponseType,

      UseTokenLifetime = appSettings.OpenIdConnect.UseTokenLifetime,
      SaveTokens = appSettings.OpenIdConnect.SaveTokens,
      GetClaimsFromUserInfoEndpoint = appSettings.OpenIdConnect.GetClaimsFromUserInfoEndpoint,

      Events = new OpenIdConnectEvents
      {
          OnTicketReceived = TicketReceived,
          OnUserInformationReceived = UserInformationReceived
      },

      TokenValidationParameters = new TokenValidationParameters
      {                    
          NameClaimType = appSettings.OpenIdConnect.NameClaimType,
          RoleClaimType = appSettings.OpenIdConnect.RoleClaimType
      }
  };
  oidcOptions.Scope.Clear();
  foreach (var scope in appSettings.OpenIdConnect.Scopes)
  {
      oidcOptions.Scope.Add(scope);
  }
  app.UseOpenIdConnectAuthentication(oidcOptions);

Und hier sind einige Veranstaltungsbeispiele, unter denen ich wählen kann:

        public async Task TicketReceived(TicketReceivedContext trc)
    {
        await Task.Run(() =>
        {
            Debug.WriteLine("TicketReceived");

            //Alternatives to get the expires_at value
            //var expiresAt1 = trc.Ticket.Properties.GetTokens().SingleOrDefault(t => t.Name == "expires_at").Value;
            //var expiresAt2 = trc.Ticket.Properties.GetTokenValue("expires_at");
            //var expiresAt3 = trc.Ticket.Properties.Items[".Token.expires_at"];

            //Outputs:
            //expiresAt1 = "2016-12-19T11:58:24.0006542+00:00"
            //expiresAt2 = "2016-12-19T11:58:24.0006542+00:00"
            //expiresAt3 = "2016-12-19T11:58:24.0006542+00:00"

            //Remove OIDC protocol claims ("iss","aud","exp","iat","auth_time","nonce","acr","amr","azp","nbf","c_hash","sid","idp")
            ClaimsPrincipal p = TransformClaims(trc.Ticket.Principal);

            //var identity = p.Identity as ClaimsIdentity;

            // keep track of access token expiration
            //identity.AddClaim(new Claim("expires_at1", expiresAt1.ToString()));
            //identity.AddClaim(new Claim("expires_at2", expiresAt2.ToString()));
            //identity.AddClaim(new Claim("expires_at3", expiresAt3.ToString()));

            //Todo: Check if it's OK to replace principal instead of the ticket, currently I can't make it work when replacing the whole ticket.
            //trc.Ticket = new AuthenticationTicket(p, trc.Ticket.Properties, trc.Ticket.AuthenticationScheme);
            trc.Principal = p;                
        });
    }

Ich habe auch das UserInformationReceived-Ereignis. Ich bin nicht sicher, ob ich dieses Ereignis anstelle des TicketReceived-Ereignisses verwenden soll.

        public async Task UserInformationReceived(UserInformationReceivedContext uirc)
    {
        await Task.Run(() =>
        {
            Debug.WriteLine("UserInformationReceived");

            ////Alternatives to get the expires_at value
            //var expiresAt4 = uirc.Ticket.Properties.GetTokens().SingleOrDefault(t => t.Name == "expires_at").Value;
            //var expiresAt5 = uirc.Ticket.Properties.GetTokenValue("expires_at");
            //var expiresAt6 = uirc.Ticket.Properties.Items[".Token.expires_at"];
            //var expiresIn1 = uirc.ProtocolMessage.ExpiresIn;

            //Outputs:
            //expiresAt4 = "2016-12-19T11:58:24.0006542+00:00"
            //expiresAt5 = "2016-12-19T11:58:24.0006542+00:00"
            //expiresAt6 = "2016-12-19T11:58:24.0006542+00:00"
            //expiresIn = "60" <-- The 60 seconds test interval for the access token lifetime is configured in the IdentityServer client configuration settings

            var identity = uirc.Ticket.Principal.Identity as ClaimsIdentity;

            //Keep track of access token expiration
            //Add a claim with information about when the access token is expired, it's possible that I instead should use expiresAt4, expiresAt5 or expiresAt6 
            //instead of manually calculating the expire time.
            //This claim will later be checked before calling Web API's and if needed a new access token will be requested via the IdentityModel2 library.
            //identity.AddClaim(new Claim("expires_at4", expiresAt4.ToString()));
            //identity.AddClaim(new Claim("expires_at5", expiresAt5.ToString()));
            //identity.AddClaim(new Claim("expires_at6", expiresAt6.ToString()));
            //identity.AddClaim(new Claim("expires_in1", expiresIn1.ToString()));
            identity.AddClaim(new Claim("expires_in", DateTime.Now.AddSeconds(Convert.ToDouble(uirc.ProtocolMessage.ExpiresIn)).ToLocalTime().ToString()));
            //identity.AddClaim(new Claim("expires_in3", DateTime.Now.AddSeconds(Convert.ToDouble(uirc.ProtocolMessage.ExpiresIn)).ToString()));

            //The following is not needed when to OIDC middleware CookieAuthenticationOptions.SaveTokens = true
            //identity.AddClaim(new Claim("access_token", uirc.ProtocolMessage.AccessToken));
            //identity.Claims.Append(new Claim("refresh_token", uirc.ProtocolMessage.RefreshToken));
            //identity.AddClaim(new Claim("id_token", uirc.ProtocolMessage.IdToken));                
        });
    }
0
Jonas