2

I am following along the IdentityServer4 Quickstart project to get up to speed on how it is implemented. My Startup.cs is close to identical, my implementation of the AccountController.cs class is more or less the same, and so on.

However, there seems to be some sort of low-level conflict with how local authentication cookies are handled.

1) In the startup, I do not call app.UseAuthentication() as IdentityServer sets that up later on. I have gone so far as to copy the services.AddAuthentication() from the quickstart project verbatim, but am still having issues.

When logging in, the Quickstart project produces two cookies: one Forgery Validation cookie, and one cookie called idsrv. Nowhere in the project is this explicitly defined to do so.

When I run my implementation of it, however, I get three cookies: A Forgery Validation cookie, an idsrv.session cookie, and an .AspNetCore.Identity.Application cookie. I can force ASP.NET to name the cookie 'idsrv', but the existence of a .session cookie leads me to believe it isn't using the right scheme.

2) When I try to log out by calling HttpContext.SignOutAsync(), the cookie is not deleted and nothing happens: it stays signed in. I have found questions with similar problems, but those seem to be a) implementing an external auth and b) implementing redirects that supposedly overwrite the signout url. I am doing neither.

My login implementation:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login( LoginInputModel model, string action )
{
   var context = await _interactionService.GetAuthorizationContextAsync( model.ReturnUrl );

   if( action != "Login" )
   {
      if( context != null )
      {
         // Handle a cancelled login
         await _interactionService.GrantConsentAsync( context, ConsentResponse.Denied );
      }
      else
         return Redirect( "~/" );
   }

   var errors = ModelState.Values.SelectMany( v => v.Errors );
   if( ModelState.IsValid )
   {
      var user = await _userManager.FindByNameAsync( model.Username );
      if( user != null && await _userManager.CheckPasswordAsync( user, model.Password ) )
      {
         await _eventService.RaiseAsync( new UserLoginSuccessEvent( user.UserName, user.Id, user.UserName ) );

         //Handle RememberMe Checkbox
         AuthenticationProperties props = null;
         if( model.RememberMe )
         {
            props = new AuthenticationProperties
            {
               IsPersistent = true,
               ExpiresUtc = DateTimeOffset.UtcNow.Add( Config.AccountRememberMeDuration ),
            };
         }

         //Issue Auth Cookie
         await HttpContext.SignInAsync( user.Id, user.UserName, props );

         if( _interactionService.IsValidReturnUrl( model.ReturnUrl ) || Url.IsLocalUrl( model.ReturnUrl ) )
            return Redirect( model.ReturnUrl );
         return Redirect( "~/" );
      }

      //If we made it this far, the authentication failed
      await _eventService.RaiseAsync( new UserLoginFailureEvent( model.Username, "Invalid Credentials" ) );
      ModelState.AddModelError( "", "Invalid Credentials" );
   }

   //Display form with error message
   model.Password = string.Empty;
   return View( model );
}

My Logout Implementation:

[HttpGet]
public async Task Logout( LogoutInputModel model )
{
   if( User?.Identity.IsAuthenticated == true )
   {
      await HttpContext.SignOutAsync();
      await _eventService.RaiseAsync( new UserLogoutSuccessEvent( User.GetSubjectId(), User.GetDisplayName() ) );
   }
}

My Startup.cs:

public void ConfigureServices( IServiceCollection services )
{
   services.AddMvc();
   services.AddIdentityServer()
      .AddOperationalStore( options =>
      {
         options.ConfigureDbContext = builder =>
             builder.UseSqlServer( Config.ConnectionString, sqlOptions => sqlOptions.MigrationsAssembly( Config.MigrationsAssembly ) );

         options.EnableTokenCleanup = true;
         options.TokenCleanupInterval = 30; //Every 30 seconds
      } )
      .AddConfigurationStore( options =>
      {
         options.ConfigureDbContext = builder =>
             builder.UseSqlServer( Config.ConnectionString, sqlOptions => sqlOptions.MigrationsAssembly( Config.MigrationsAssembly ) );
      } )
      .AddDeveloperSigningCredential();

   services.AddDbContext<CustomUserContext>( builder =>
      builder.UseSqlServer( Config.ConnectionString, sqlOptions => sqlOptions.MigrationsAssembly( Config.MigrationsAssembly ) )
   );
   services.AddIdentity<CustomUser, CustomRole>()
      .AddEntityFrameworkStores<CustomUserContext>();

   services.AddAuthentication();
}

public void Configure( IApplicationBuilder app, IHostingEnvironment env )
{
   if( env.IsDevelopment() )
      app.UseDeveloperExceptionPage();


   app.UseIdentityServer();
   app.UseStaticFiles();
   app.UseMvcWithDefaultRoute(); //TODO: Routes

}

How I'm calling the logout function:

<div class="account-actions">
    @using( Html.BeginForm( "Logout", "Account", FormMethod.Get ) )
    {
        <input type="submit"
               class="account-action-button account-action-logout"
               value="Logout" />
    }
</div>
w0f
  • 908
  • 9
  • 23
  • Which type of client are you using (Ex: MVC)? – tha4 Jun 04 '18 at 05:59
  • @tha4 I'm using MVC directly baked into the IdentityServer project – w0f Jun 04 '18 at 12:29
  • As @bychkov mentioned, most probable you have not configured to cookie middleware properly. I'm not sure that its possible to cohost a MVC app with IDS4 because both depends on Cookie middleware to signing in. It is possible to cohost Web API and IDS4 because it will use bearer tokens for authentication (https://github.com/brockallen/IdentityServerAndApi/blob/master/IdentityServerHost/Startup.cs) – tha4 Jun 05 '18 at 03:02

2 Answers2

2

I had the same issue. I thought I might try to call SignOutAsync( schema ) instead. By trying to sign out of a schema that didn't exist I got an error message with the list of schemas supported. One of them was "Identity.Application" and doing a sign out on that worked. e.g. await this.HttpContext.SignOutAsync( "Identity.Application" );

Mike Bennett
  • 266
  • 3
  • 10
  • nice, thanks for that, was wondering why my cookies still persisted, was not aware that ms identity added something there. – modmoto Sep 01 '21 at 13:40
  • I used your hint and tried to sign out from *donkey*, producing the same error. Then I tried `SignOutAsync` on **every** of the listed schemas. No errors. But the stupid cookies are still there! One is `idsrv.session` and the other `.AspNetCore.Identity.Application`. Any suggestions? Gainful observations? Hints? – Konrad Viltersten Sep 24 '21 at 08:03
1

As far as I can see you are cohosting the IdentityServer within your MVC app, and I believe you need to configure Cookie middleware for your MVC app. Configuration you have right now in Startup.cs is for IdentityServer itself. And the cookie you get is the one for the IdentityServer.

Take a look at how you configure cookie authentication + OIDC in this sample: https://github.com/IdentityServer/IdentityServer4.Samples/blob/release/Clients/src/MvcHybrid/Startup.cs

bychkov
  • 1,503
  • 1
  • 13
  • 26
  • I was playing with ASPNet Identity Core and IdentityServer and I now see that you are calling services.AddIdentity but IdS docs mention that you need to call it before setting IdS: http://docs.identityserver.io/en/release/quickstarts/6_aspnet_identity.html – bychkov Jun 04 '18 at 23:19
  • Note Here's the quote from the docs: It’s important when using ASP.NET Identity that IdentityServer be registered after ASP.NET Identity in the DI system because IdentityServer is overwriting some configuration from ASP.NET Identity. – bychkov Jun 04 '18 at 23:20