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 868 forks source link

Unable to disable the defaultUI in asp.net core 2.1 #1706

Closed ryhled closed 6 years ago

ryhled commented 6 years ago

Hi!

Removing AddDefaultUI() (and removing the area) does not seem to clear the default UI.

If i remove this from the default template and try to access /Identity/Account/Register i recieve "InvalidOperationException: A suitable constructor for type 'Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal.RegisterModel' could not be located" when i expect to get a 404.

Is this caused by something i missed perhaps or is this an active issue?

Eilon commented 6 years ago

@javiercn can you help answer this?

Eilon commented 6 years ago

@baseless if you can show the stack trace of the exception that would also be helpful. Thanks!

javiercn commented 6 years ago

And the startup code you are using

ryhled commented 6 years ago

@Eilon the only steps i did was:

  1. Create new 2.1 Web application (installed vs2017 preview 2 update earlier) with individual user accounts auth.
  2. Remove the 'AddDefaultUI()' and deleted the 'Areas' folder.
  3. Run project (IIS Express) and visit /Identity/Account/Register in browser.

Startup code (after i removed AddDefaultUI):

public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<CookiePolicyOptions>(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });

            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(
                    Configuration.GetConnectionString("DefaultConnection")));
            services.AddIdentity<IdentityUser, IdentityRole>(options => options.Stores.MaxLengthForKeys = 128)
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders();

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseBrowserLink();
                app.UseDeveloperExceptionPage();
                app.UseDatabaseErrorPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseCookiePolicy();

            app.UseAuthentication();

            app.UseMvc();
        }
    }

The raw exception details are:

System.InvalidOperationException: A suitable constructor for type 'Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal.RegisterModel' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor.
   at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.FindApplicableConstructor(Type instanceType, Type[] argumentTypes, ConstructorInfo& matchingConstructor, Nullable`1[]& parameterMap)
   at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.CreateFactory(Type instanceType, Type[] argumentTypes)
   at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.DefaultPageModelActivatorProvider.CreateActivator(CompiledPageActionDescriptor actionDescriptor)
   at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.DefaultPageModelFactoryProvider.CreateModelFactory(CompiledPageActionDescriptor descriptor)
   at Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvokerProvider.CreateCacheEntry(ActionInvokerProviderContext context, FilterItem[] cachedFilters)
   at Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvokerProvider.OnProvidersExecuting(ActionInvokerProviderContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ActionInvokerFactory.CreateInvoker(ActionContext actionContext)
   at Microsoft.AspNetCore.Mvc.Internal.MvcAttributeRouteHandler.<>c__DisplayClass12_0.<RouteAsync>b__0(HttpContext c)
   at Microsoft.AspNetCore.Builder.RouterMiddleware.<Invoke>d__4.MoveNext()
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.<Invoke>d__6.MoveNext()
   at Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.MigrationsEndPointMiddleware.<Invoke>d__4.MoveNext()
   at Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.DatabaseErrorPageMiddleware.<Invoke>d__6.MoveNext()
   at Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.DatabaseErrorPageMiddleware.<Invoke>d__6.MoveNext()
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.<Invoke>d__7.MoveNext()

I guess it should be easily reproducable (as long as it isnt caused by something locally on my end). But i can try to fetch whatever you need :).

javiercn commented 6 years ago

This is enough to investigate I think. We changed this area substantially on Preview2, so it might not be super relevant. But I'll make sure we look into it.

Thanks for bringing it up.

javiercn commented 6 years ago

At a minimum we should have an in-memory E2E test that checks this. Given the churn that we had in the area I'm going to suggest here that we start by adding a test on the new implementation to confirm the behavior.

javiercn commented 6 years ago

This doesn't repro in preview2. I've added tests to verify it as part of https://github.com/aspnet/Identity/pull/1749

ekhtiari commented 6 years ago

@javiercn Hi I have same problem but i receive this error on my windows server not in my visual studio is there any thing to update or some component to register or something else ? Thanks

javiercn commented 6 years ago

@ekhtiari can you provide a minimal repro project?

ekhtiari commented 6 years ago

@javiercn i find main problem I init new project with identity module ( New default UI for identity). everything is good in visual studio or when i use dotnet run command in project folder but when i publish project i receive this error i try many things like : 1.use this line in view file @model Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal.LoginModel instance @model Loginmodel

  1. add these code to _layout.cshtml @using Microsoft.AspNetCore.Hosting @using Microsoft.AspNetCore.Mvc.ViewEngines

i thing when i use this command to generate identity page , something not work correctly : dotnet aspnet-codegenerator identity -dc WebApplication.Data.ApplicationDbContext

i test new init project and publish it and everything was correctly but when i use code generator to create identity page i receive many error about login and register and other thing..

so .. how can i fix this ?

javiercn commented 6 years ago

@ekhtiari I don't think I have enough information to help you troubleshoot the issue you're running into. Can you provide a repro project showing the issue?

ekhtiari commented 6 years ago

@javiercn in this repro if you run project in tool folder everything is good and login part and register part is work correctly but if run project in published folder ( bin/Release/PublishOutput/) and use login or register you see my error. identity ui a question : is global tooling cause my problem ? i forgot any thing to install ? i install last version on net core .

chrisdpratt commented 6 years ago

I'm running 2.1.0-rc1-final, and I cannot disable the default UI. I have no Areas directory, no Pages directory, and I'm not explicitly calling AddDefaultUI() in Startup.cs. Still, when I go to a protected action, I get redirected to /Identity/Account/Login, which actually loads just fine with the default UI. However, I have my own MVC controllers and actions, specific my own sign in action that should be used as the login URL. I even explicitly added the following in Startup.cs:

services.ConfigureApplicationCookie(options =>
{
    options.LoginPath = "/signin";
    options.LogoutPath = "/signout";
    options.AccessDeniedPath = "/signin";
});

This seems to be being entirely ignored, or more likely, overwritten by the default UI's IdentityHostingStartup.cs.

The default UI should be entirely opt-in. In other words, if you don't add AddDefaultUI() or actually scaffold it into your project, it should not exist at all. Even better, I think this should be a separate NuGet package, simply to make adding it entirely explicit.

UPDATE

So, I found the problem was in using services.AddDefaultIdentity. You must use services.AddIdentity instead, which oddly makes you specify a TRole type param, when neither AddDefaultIdentity nor AddIdentityCore do. Regardless, that solves the problem of the default UI being included, but you still get redirected to /Identity/Accounts/Login. To fix that, you need to explicitly change the URLs via services.ConfigureApplicationCookie and make sure that that is after the call to services.AddIdentity. Then, everything works as it should.

UPDATE 2

So using AddIdentity led to some exceptions as it still doesn't add all the necessary services apparently. I dug in to what AddDefaultIdentity is doing and recreated it sans AddDefaultUI.

services.AddAuthentication(o =>
{
    o.DefaultScheme = IdentityConstants.ApplicationScheme;
    o.DefaultSignInScheme = IdentityConstants.ExternalScheme;
})
.AddIdentityCookies(o => { });

services.AddIdentityCore<ApplicationUser>(o =>
{
    o.Stores.MaxLengthForKeys = 128;
})
.AddSignInManager()
.AddDefaultTokenProviders();

services.ConfigureApplicationCookie(o =>
{
    o.LoginPath = "/login";
    o.LogoutPath = "/logout";
    o.AccessDeniedPath = "/access-denied";
});

There should really be an extension method that bootstraps a default Identity setup without the default UI included, and then either just have devs add AddDefaultUI if they want it, or have an extension method that does the default stuff and that. It's kind of ridiculous that you have to resort to defining all this in your Startup.cs instead of the much more simplistic services.AddDefaultIdentity, just because you don't want the default UI as well.

javiercn commented 6 years ago

but you still get redirected to /Identity/Accounts/Login.

Can you share a small repro project for this? The only time when we update the cookie paths is when you call to AddDefaultIdentity()

andyfurniss4 commented 6 years ago

@chrisdpratt I've followed your solution above and I've created a new controller for managing the login and logout requests but I'm getting stuck in a redirection loop on the login page.

Startup:

services.AddAuthentication(o =>
{
    o.DefaultScheme = IdentityConstants.ApplicationScheme;
    o.DefaultSignInScheme = IdentityConstants.ExternalScheme;
})
.AddIdentityCookies(o => { });

services.AddIdentityCore<IdentityUser>(o =>
{
    o.Stores.MaxLengthForKeys = 128;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddSignInManager()
.AddDefaultTokenProviders();

services.ConfigureApplicationCookie(o =>
{
    o.LoginPath = "/Identity/Account/Login";
    o.LogoutPath = "/Identity/Account/Logout";
});

services.AddAuthentication().AddGoogle(googleOptions =>
{
    var settings = Configuration.GetSection("Google");
    googleOptions.ClientId = settings.GetValue<string>("LogInClientId");
    googleOptions.ClientSecret = settings.GetValue<string>("LogInClientSecret");
    googleOptions.AuthorizationEndpoint += "?hd=domain.com&prompt=select_account";

    googleOptions.Events = new OAuthEvents
    {
        OnCreatingTicket = context =>
        {
            string domain = context.User.Value<string>("domain");
            if (domain != "domain.com")
                throw new GoogleAuthenticationException("You must sign in with a domain.com email address");

            return Task.CompletedTask;
        }
    };
});

// MVC
services.AddMvc(options =>
{
    if (HostingEnvironment.IsDevelopment())
    {
        var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
        options.Filters.Add(new AuthorizeFilter(policy));
    }
    else
    {
        options.Filters.Add(new AllowAnonymousFilter());
    }
    options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

AccountController:

[Area("Identity")]
public class AccountController : Controller
{
    [AllowAnonymous]
    public async Task Login(string returnUrl = "/")
    {
        await HttpContext.ChallengeAsync(new AuthenticationProperties() { RedirectUri = returnUrl });
    }

    public async Task Logout()
    {
        await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme, new AuthenticationProperties
        {
            RedirectUri = Url.Action("Login", "Account", new { area = "Identity" })
        });
    }
}
PendelinP commented 6 years ago

Hi there, as @chrisdpratt already mentioned here it would be cool to have a simple without the default UI included. Since all my login UIs are handled via an Angular WebApp the server-side pages are unnecessary.

phantom2017-Nov commented 6 years ago

In this age of "decoupled" services, I am not sure whose brilliant idea it was to tightly couple the core Identity implementation with a "default" UI ! Sorry to be blunt, this is probably among the dumbest ideas I have encountered in a long time. I hope in the next version, sanity prevails & they at least provide a clean way to not include the "default" UI. The default identity template project (in previous versions) with AccountManager implementations etc, worked just fine - yes, we had to do more work to provide our own UI pages for all identity stuff, but that provided excellent flexibility. To couple the core identity implementation along with some "default" (useless) UI (where everything would probably have to be over ridden) into ONE "Razor library" is nothing short of a disaster. I for one am not interested in "scaffolding" on top of your default UI.

phantom2017-Nov commented 6 years ago

@andyfurniss4 I've followed your solution above and I've created a new controller for managing the login and logout requests but I'm getting stuck in a redirection loop on the login page.

I think you may be missing the call to app.UseAuthentication(); (before app.UseMvc , typically in the Configure method) ... the UseAuthentication() call causes the authentication cookie to be passed on requests subsequent to a successful Login, I used the same Services configuration you have suggested and had the same issue till I realized that I was missing the "UseAuthentication" call ..