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.
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 Antiforgery 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: An Antiforgery 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.
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>
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" );