aspnet / Identity

[Archived] ASP.NET Core Identity is the membership system for building ASP.NET Core web applications, including membership, login, and user data. Project moved to https://github.com/aspnet/AspNetCore
Apache License 2.0
1.96k stars 866 forks source link

No sign-out authentication handler is registered for the scheme 'Identity.External' #2082

Closed OphiCA closed 6 years ago

OphiCA commented 6 years ago

Hello,

For several months now I've been trying to solve an issue with Identity, where 30-min on the dot after signing in, I am greeted by an Invalid Operation Exception:

InvalidOperationException: No sign-out authentication handler is registered for the scheme 'Identity.External'. The registered sign-out schemes are: Identity.Application. Did you forget to call AddAuthentication().AddCookies("Identity.External",...)?

Since it was on my blog site, I didn't put too much effort into it, even though it was an annoyance. Now, I'm looking into possibly some real world client work where I'd like to use ASP.NET Core with Identity, but I can't be having this same issue.

Could someone please tell me why this exception throws? I don't want to use external schemes, so why is it trying to sign out of one? When did it sign into one?

Here's the relevant code from my app, hopefully someone can point me in the right direction because I sure as heck can't.

Startup.cs

public sealed class Startup {
    public void ConfigureServices(
        IServiceCollection services) {
        //...
        services.AddApplicationIdentity();
        //...
        services.ConfigureApplicationCookie(
            o => {
                o.Cookie.SameSite = SameSiteMode.Strict;
                o.Cookie.SecurePolicy = CookieSecurePolicy.Always;

                o.AccessDeniedPath = new PathString("/admin");
                o.ExpireTimeSpan = TimeSpan.FromHours(4);
                o.LoginPath = new PathString("/admin");
                o.LogoutPath = new PathString("/admin/sign-out");
            });
        services.Configure<IdentityOptions>(
            o => {
                o.Password.RequiredLength = 8;

                o.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
                o.Lockout.MaxFailedAccessAttempts = 5;
            });
    }

    public void Configure(
        IApplicationBuilder app) {
        //...
        app.UseAuthentication();
        //...
    }
}

ServiceCollectionExtensions

public static class ServiceCollectionExtensions {
    public static IdentityBuilder AddApplicationIdentity(
        this IServiceCollection services) {
        services.AddAuthentication(
            o => {
                o.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
                o.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
                o.DefaultSignInScheme = IdentityConstants.ApplicationScheme;
            }).AddCookie(IdentityConstants.ApplicationScheme,
            o => {
                o.Events = new CookieAuthenticationEvents {
                    OnValidatePrincipal = SecurityStampValidator.ValidatePrincipalAsync
                };
            });

        services.TryAddScoped<IdentityErrorDescriber>();
        services.TryAddScoped<ILookupNormalizer, UpperInvariantLookupNormalizer>();
        services.TryAddScoped<IPasswordHasher<User>, PasswordHasher<User>>();
        services.TryAddScoped<IPasswordValidator<User>, PasswordValidator<User>>();
        services.TryAddScoped<IUserClaimsPrincipalFactory<User>, UserClaimsPrincipalFactory<User>>();
        services.TryAddScoped<UserManager<User>>();
        services.TryAddScoped<IUserStore<User>, ApplicationUserStore>();
        services.TryAddScoped<IUserValidator<User>, UserValidator<User>>();
        services.TryAddScoped<ISecurityStampValidator, SecurityStampValidator<User>>();
        services.TryAddScoped<SignInManager<User>>();

        return new IdentityBuilder(typeof(User), services);
    }
}

I'm not going to include the ApplicationUserStore because I highly doubt it has anything to do with the exception. I'd appreciate any help because I am just struggling at this point. Thanks!

HaoK commented 6 years ago

SignInManager.SignOut expects there to be an external cookie registered as well which is where that signout call is coming, you can either override SignInManager's SignOut method here: https://github.com/aspnet/Identity/blob/master/src/Identity/SignInManager.cs#L206

Or you can add all of the identity cookies.

OphiCA commented 6 years ago

@HaoK Thanks for the pointer, it looks like it fixed the exception. I was wondering why the SignOut method was being called, and now that the exception is no longer an issue I'm simply redirected to the login screen. I guess the session was expiring and thus triggering the SignOut method.

So, my next question is, why is it that my session is set for only 30 minutes? In the ConfigureApplicationCookie I do set the ExpireTimeSpan to four hours, so why is it being signed out earlier? Do I need to set the Cookie.Expiration property too? I thought the ExpireTimeSpan is the recommended property for controlling the lifetime of a cookie?

Thanks again!

HaoK commented 6 years ago

The security stamp validator runs every 30 minutes and that's probably invalidating your cookie, you can try removing the registration of that to see if that stops the behavior you are seeing

OphiCA commented 6 years ago

@HaoK I figured it out! After your last comment I downloaded a copy of the solution and followed along the method calls to see where I was failing. After a few hours, it turned out that I, for whatever reason months ago, decided not to store a security stamp in the database. I guess I was being lazy since I was going to be the only user, not really sure? Anyway, once I added it in everything started working as I expected it to.

Thank you for pointing me in the right direction, I really appreciate it!