Search code examples
identityserver4

Why is Identity Server4 Logout not working? (without MS Identity)


I am trying to implement my own OAuth Server with IdentityServer4, and so far everything works except the logout.

I am not using Microsoft Identity, as I already have an existing WebApp with a WebApi which is handling the user-related CRUD operations. Thus I am using an existing Database for fetching Users and validating their username and PW. If validation is successful, my validation Method returns an object of type "AuthenticatedUser" (which is a UtilityClass I made).

Edit My Client is a Xamarin App, and using IdentityModel.OidcClient2 for login. I am testing with the UWP platform, Edit which uses WebAuthenticationBroker for Login/Logout calls.

Code I use is the one from the QuickStart UI Example, with a small modification to validate the users from my existing DB: Edit Now I am explicitly creating Claims, ClaimsIdentity, and added CookieAuthenticationDefaults.AuthenticationScheme wherever possible.

//my method for user validation
AuthenticatedUser user = await _userService.ValidateCredentials(model.Username, model.Password);

//rest of login code from quickstart ui
if (user != null) 
{ 
    await _events.RaiseAsync(new UserLoginSuccessEvent(user.FirstName, user.Id.ToString(), user.FirstName));

    // only set explicit expiration here if user chooses "remember me". 
    // otherwise we rely upon expiration configured in cookie middleware.
    AuthenticationProperties props = null;
    if (AccountOptions.AllowRememberLogin && model.RememberLogin)
    {
        props = new AuthenticationProperties
         {
        IsPersistent = true,
        ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration)
        };
    };

    //things we know about the user that we wish to store on the cookie
    var claims = new List<Claim>
    {
        new Claim(JwtClaimTypes.Role, user.RoleId.ToString()),
        new Claim(JwtClaimTypes.Name, user.FirstName + " " + user.LastName),
        new Claim(JwtClaimTypes.Subject, user.Id.ToString())
    };        

    var userIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);

    ClaimsPrincipal principal = new ClaimsPrincipal(userIdentity);

    //set the cookie using the SignInAsync method
    await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal, props);
    // issue authentication cookie with subject ID and username
    await HttpContext.SignInAsync(user.Id.ToString(), user.FirstName, props);

    //....

So far, this seems to work well. When the Login fails, I cannot access my protected Api, if the login succeeds, I get an AccessToken with the claims I requested, and I can access the protected Api methods as expected.

When I call the logout endpoint (done by a HTTP request to the endpoint, providing id_token_hint as query parameter), though, for some reason the User is not Authenticated - therefore my User is never Logged out by calling HttpContext.SignOutAsync().

        if (User?.Identity.IsAuthenticated == true) //always evaluates to false?! why?
        {
            // delete local authentication cookie
            await HttpContext.SignOutAsync();

            // raise the logout event
            await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName()));
        }

Edit After using WebAuthenticationBroker instead of a simple HTTP Request for calling the logout endpoint, the console Logs though state that "XamarinApp" got logged out. Even though HttpContext.SignOutAsync() was never called What does this mean? I doubt that this is Ok, but the app behaves as I want afterwards, e.g I can log in with a new user.

[16:43:12 Debug] IdentityServer4.Hosting.EndpointRouter
Request path /connect/endsession matched to endpoint type Endsession

[16:43:12 Debug] IdentityServer4.Hosting.EndpointRouter
Endpoint enabled: Endsession, successfully created handler: IdentityServer4.Endpoints.EndSessionEndpoint

[16:43:12 Information] IdentityServer4.Hosting.IdentityServerMiddleware
Invoking IdentityServer endpoint: IdentityServer4.Endpoints.EndSessionEndpoint for /connect/endsession

[16:43:12 Debug] IdentityServer4.Endpoints.EndSessionEndpoint
Processing signout request for anonymous

[16:43:12 Debug] IdentityServer4.Validation.EndSessionRequestValidator
Start end session request validation

[16:43:12 Debug] IdentityServer4.Validation.TokenValidator
Start identity token validation

[16:43:12 Debug] IdentityServer4.EntityFramework.Stores.ClientStore
xamarinApp found in database: True

[16:43:12 Debug] IdentityServer4.Validation.TokenValidator
Client found: xamarinApp / Xamarin App

[16:43:12 Debug] IdentityServer4.Validation.TokenValidator
Calling into custom token validator: IdentityServer4.Validation.DefaultCustomTokenValidator

[16:43:12 Debug] IdentityServer4.Validation.TokenValidator
Token validation success
{
  //Token details omitted here for the sake of simplicity. 
  }
}

[16:43:12 Information] IdentityServer4.Validation.EndSessionRequestValidator
End session request validation success
{
  "ClientId": "xamarinApp",
  "ClientName": "Xamarin App",
  "SubjectId": "unknown",
  "PostLogOutUri": "xamarinformsclients://callback",
  "Raw": {
    "id_token_hint": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjA3RjlGQ0VFRTVCMzM4ODkzODZCNjc2MTZCRjZCOTFEMUEwRkRBQjAiLCJ0eXAiOiJKV1QiLCJ4NXQiOiJCX244N3VXek9JazRhMmRoYV9hNUhSb1AyckEifQ.eyJuYmYiOjE1Mjg5MDA5ODYsImV4cCI6MTUyODkwMTI4NiwiaXNzIjoiaHR0cHM6Ly9sYXB0b3AtMW0waW4zMW46NDQzODciLCJhdWQiOiJ4YW1hcmluQXBwIiwibm9uY2UiOiI4YjZjZWRkMDFhMjQ0ZDJmOWY3ZGM4NzZmM2NmZGYwNiIsImlhdCI6MTUyODkwMDk4NiwiYXRfaGFzaCI6IkZualBtd2hiZTNmOVRITjEzM0NSZWciLCJzaWQiOiJkMmJlZTgyYzg0YWY2NGI5ZDUyYmZlNmExNmU1MTNmZiIsInN1YiI6IjI4IiwiYXV0aF90aW1lIjoxNTI4OTAwOTgzLCJpZHAiOiJsb2NhbCIsInVzZXJfaWQiOiIyOCIsInJvbGVfaWQiOiI0IiwibmFtZSI6IlRpbGwgU2F1YmVybWFubiIsImZhbWlseV9uYW1lIjoiU2F1YmVybWFubiIsImFtciI6WyJwd2QiXX0.ZjwL8nuq-WD3D-pXruZtE_I5TyNNO_ZMabz2JiKVnTaTnITwGV5CIJcLcWSpBCOyaSFXKUicAtROeWLReuk_LWoUTKXcX7lyv5VP9-ItBNA13EwgsbhQX7BgS2lbE9fQU7OgGARJcpvPKaT9FabFtEZsNYW9sNeBo-6CUPkYtVH_rjRyLihFi2NlZlkHBc7_oPE0hsjf61QIwyGZEhVXvDXkP_Q9t_Bfr3_QrUF6MfyhzLs0KcMwbtlWUxYw51J8phz7RPUXbbiZ1tG9Ay4DNy8RZbzfI-uFAbrqH7waLo_f5JO15eYc-xICl22ZS_4lW0_MlzP_rq46PnGOwNBqlg",
    "post_logout_redirect_uri": "xamarinformsclients://callback"
  }
} 

Edit As far as I can understand, this probably has to do with my Xamarin Client and Cookies. I found tutorials on how to configure a MVC Client, IDSVR4 and the Cookie Middleware, but nothing regarding native Apps, IDSVR4 and Cookie Middleware.

How is IDSVR4 (or the logout in particular) supposed to work with a non-MVC Client and IdentityModel.OidcClient?


Solution

  • Finally I found the reason. In the QuickstartUI Examples, the Class "AccountConteroller.cs" sets Explicit Expiration only if the user chooses the "remember me" Option. I removed the if condition, and finally the authentication cookie is properly stored, on logout my user is not null anymore, and everything is fine.

    class AccountController
    ...
    
    AuthenticationProperties props = null;
    //ALWAYS SET EXPLICIT EXPIRATION, SO COOKIE CAN BE DELETED WHEN LOGGING OUT
    //if (AccountOptions.AllowRememberLogin && model.RememberLogin)
    //{
        props = new AuthenticationProperties
        {
            IsPersistent = true,
            ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration)
        };
    

    // };