0

I've found very helpful cordova example about social login https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Samples/ I've built my own application on top of this sample and I'm stuck with very weird problem. First output is from example, the second from my app. I don't know why in my application "Identity.External" authentication scheme is used instead of "ServerCookie". This cause authentication failed.

Output from example:

Microsoft.AspNetCore.Hosting.Internal.WebHost: Information: Request starting HTTP/1.1 GET http://localhost:54540/signin-facebook?code=***  
Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware: Information: AuthenticationScheme: ServerCookie signed in.

and ouptput from my application:

Microsoft.AspNetCore.Hosting.Internal.WebHost: Information: Request starting HTTP/1.1 GET http://localhost:62892/signin-facebook?code=***
Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware: Information: AuthenticationScheme: Identity.External signed in.

Full output:

Microsoft.AspNetCore.Hosting.Internal.WebHost: Information: Request starting HTTP/1.1 GET http://localhost:62892/signin-facebook?code=***
Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware: Information: AuthenticationScheme: Identity.External signed in.
Microsoft.AspNetCore.Hosting.Internal.WebHost: Information: Request finished in 1088.8914ms 302 
Microsoft.AspNetCore.Hosting.Internal.WebHost: Information: Request starting HTTP/1.1 GET     http://localhost:62892/connect/authorize?client_id=myClient&redirect_uri=http://localhost:62892/competitions/dashboard&response_type=token&provider=Facebook  
Microsoft.AspNetCore.Authorization.DefaultAuthorizationService: Information: Authorization failed for user: .
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker: Warning: Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter'.
Microsoft.AspNetCore.Mvc.ChallengeResult: Information: Executing ChallengeResult with authentication schemes ().
Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware: Information: AuthenticationScheme: ServerCookie was challenged.
Microsoft.AspNetCore.Hosting.Internal.WebHost: Information: Request finished in 45.0627ms 302 
Microsoft.AspNetCore.Hosting.Internal.WebHost: Information: Request starting HTTP/1.1 GET http://localhost:62892/signin?ReturnUrl=%2Fconnect%2Fauthorize%3Fclient_id%3DmyClient%26redirect_uri%3Dhttp%3A%2F%2Flocalhost%3A62892%2Fcompetitions%2Fdashboard%26response_type%3Dtoken%26provider%3DFacebook  
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker: Information: Executing action method FlyingDuck.Web.Controllers.Api.AuthenticationController.SignIn (FlyingDuck.Web) with arguments (/connect/authorize?client_id=myClient&redirect_uri=http://localhost:62892/competitions/dashboard&response_type=token&provider=Facebook) - ModelState is Valid

My Startup.cs file content:

namespace MyProject.Web
{
    public class Startup
    {
        public Startup(IHostingEnvironment env)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
            if (env.IsDevelopment())
            {
                builder.AddUserSecrets();
            }
            var wwwrootRoot = env.WebRootPath;
            builder.AddEnvironmentVariables();
            Configuration = builder.Build();
        }

        public IConfigurationRoot Configuration { get; }

        public IServiceProvider ConfigureServices(IServiceCollection services)
        {
            services.AddAuthentication(options =>
            {
                options.SignInScheme = "ServerCookie";
            });  
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(Configuration.GetConnectionString("MyEntities")));
            services.Configure<PasswordHasherOptions>(options =>
            {
                options.CompatibilityMode = PasswordHasherCompatibilityMode.IdentityV2;
            });
            services.AddScoped(ctx => new MyEntities(Configuration.GetConnectionString("MyEntities")));
            services.AddScoped<ViewRender, ViewRender>();
            services.AddIdentity<ApplicationUser, IdentityRole<Guid>>(
                options =>
                {
                    options.User.RequireUniqueEmail = true;
                    options.Password.RequireUppercase = false;
                    options.Password.RequireDigit = false;
                    options.Password.RequireNonAlphanumeric = false;
                }
                )
                .AddEntityFrameworkStores<ApplicationDbContext, Guid>()
                .AddDefaultTokenProviders();
            services.AddSingleton(factory => new JsonSerializerSettings
            {
                ContractResolver = new CamelCasePropertyNamesContractResolver(),
                TypeNameHandling = TypeNameHandling.All
            });
            services.AddSession();
            services.AddMvc();
            services.AddSignalR(options =>
            {
                options.Hubs.EnableDetailedErrors = true;
                options.Hubs.EnableJavaScriptProxies = false;
            });
            services.AddCors(o => o.AddPolicy("MyPolicy", b =>
            {
                b.AllowAnyOrigin()
                .AllowAnyMethod()
                .AllowAnyHeader();
            }));
            services.AddTransient<IEmailSender, AuthMessageSender>();
            services.AddTransient<ISmsSender, AuthMessageSender>();
            services.AddSingleton<IConfiguration>(Configuration);
            services.AddSingleton<IConnectionManager, ConnectionManager>();
            // This code is responsible for disable redirect on unauthorized request.
            // We don't need this feature. We want to get 401 instead of 302 redirection.
            // http://stackoverflow.com/a/34332290
            services.Configure<IdentityOptions>(options =>
            {
                options.Cookies.ApplicationCookie.AutomaticAuthenticate = true;
                options.Cookies.ApplicationCookie.AutomaticChallenge = false;
                options.Cookies.ApplicationCookie.AuthenticationScheme = "ApplicationCookie";
            });
            var builder = new ContainerBuilder();
            builder.RegisterModule(new ServicesModule(Configuration));
            builder.Populate(services);
            var container = builder.Build();
            return container.Resolve<IServiceProvider>();
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();
            app.UseIdentity();
            app.UseStatusCodePages();
            app.UseWhen(context => context.Request.Path.StartsWithSegments(new PathString("/api")), branch =>
            {
                branch.UseOAuthValidation(new OAuthValidationOptions
                {
                    AutomaticAuthenticate = true,
                    AutomaticChallenge = true
                });
            });
            app.UseWhen(context => !context.Request.Path.StartsWithSegments(new PathString("/api")), branch =>
            {
                branch.UseCookieAuthentication(new CookieAuthenticationOptions
                {
                    AutomaticAuthenticate = true,
                    AutomaticChallenge = true,
                    AuthenticationScheme = "ServerCookie",
                    CookieName = CookieAuthenticationDefaults.CookiePrefix + "ServerCookie",
                    ExpireTimeSpan = TimeSpan.FromMinutes(60),
                    LoginPath = new PathString("/signin"),
                    LogoutPath = new PathString("/signout")
                });
                branch.UseFacebookAuthentication(new FacebookOptions()
                {
                    AppId = "<removed>",
                    AppSecret = "<removed>"
                });

            });
            app.UseOpenIdConnectServer(options =>
            {
                options.Provider = new AuthorizationProvider();
                options.TokenEndpointPath = "/token";
                options.AuthorizationEndpointPath = "/connect/authorize";
                options.LogoutEndpointPath = "/connect/logout";
                options.SigningCredentials.AddEphemeralKey();
                options.ApplicationCanDisplayErrors = true;
                options.AllowInsecureHttp = true;
            });
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseDatabaseErrorPage();
                app.UseBrowserLink();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }
            app.UseStaticFiles();
            app.UseSession();
            app.UseWebSockets();
            app.UseSignalR();
            app.UseMvc(routes =>
            {
                routes.MapRoute("SignalR", "signalr/{*all}");
                routes.MapRoute("Home", "home/{*all}", new { controller = "Home", action = "Index" });
            });
            app.UseMvcWithDefaultRoute();
        }
    }
}

AuthorizationController content is the same as in the example:

namespace MyProject.Web.Controllers.Api
{
    [EnableCors(policyName: "MyPolicy")]
    public class AuthorizationController : Controller
    {
        public AuthorizationController() {}

        [Authorize, HttpGet("~/connect/authorize"), HttpPost("~/connect/authorize")]
        public IActionResult Authorize()
        {
            var response = HttpContext.GetOpenIdConnectResponse();
            if (response != null)
            {
                return View("Error", response);
            }
            var request = HttpContext.GetOpenIdConnectRequest();
            if (request == null)
            {
                return View("Error", new OpenIdConnectMessage
                {
                    Error = OpenIdConnectConstants.Errors.ServerError,
                    ErrorDescription = "An internal error has occurred"
                });
            }
            return Accept();
        }

        [Authorize, HttpPost("~/connect/authorize/accept")]
        public IActionResult Accept()
        {
            var response = HttpContext.GetOpenIdConnectResponse();
            if (response != null)
            {
                return View("Error", response);
            }
            var request = HttpContext.GetOpenIdConnectRequest();
            if (request == null)
            {
                return View("Error", new OpenIdConnectMessage
                {
                    Error = OpenIdConnectConstants.Errors.ServerError,
                    ErrorDescription = "An internal error has occurred"
                });
            }
            var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
            foreach (var claim in HttpContext.User.Claims)
            {
                claim.SetDestinations(OpenIdConnectConstants.Destinations.AccessToken,
                                      OpenIdConnectConstants.Destinations.IdentityToken);
                identity.AddClaim(claim);
            }
            var ticket = new AuthenticationTicket(
                new ClaimsPrincipal(identity),
                new AuthenticationProperties(),
                OpenIdConnectServerDefaults.AuthenticationScheme);
            ticket.SetScopes(new[] {
                /* openid: */ OpenIdConnectConstants.Scopes.OpenId,
                /* email: */ OpenIdConnectConstants.Scopes.Email,
                /* profile: */ OpenIdConnectConstants.Scopes.Profile,
                /* offline_access: */ OpenIdConnectConstants.Scopes.OfflineAccess
            }.Intersect(request.GetScopes()));
            ticket.SetResources("resource_server");
            return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
        }

        [Authorize]
        [HttpPost("~/connect/authorize/deny")]
        public IActionResult Deny()
        {
            var response = HttpContext.GetOpenIdConnectResponse();
            if (response != null)
            {
                return View("Error", response);
            }
            var request = HttpContext.GetOpenIdConnectRequest();
            if (request == null)
            {
                return View("Error", new OpenIdConnectMessage
                {
                    Error = OpenIdConnectConstants.Errors.ServerError,
                    ErrorDescription = "An internal error has occurred"
                });
            }
            return Forbid(OpenIdConnectServerDefaults.AuthenticationScheme);
        }

        [HttpGet("~/connect/logout")]
        public async Task<IActionResult> Logout(CancellationToken cancellationToken)
        {
            var response = HttpContext.GetOpenIdConnectResponse();
            if (response != null)
            {
                return View("Error", response);
            }
            var identity = await HttpContext.Authentication.AuthenticateAsync(OpenIdConnectServerDefaults.AuthenticationScheme);
            var request = HttpContext.GetOpenIdConnectRequest();
            if (request == null)
            {
                return View("Error", new OpenIdConnectMessage
                {
                    Error = OpenIdConnectConstants.Errors.ServerError,
                    ErrorDescription = "An internal error has occurred"
                });
            }
            return View("Logout", Tuple.Create(request, identity));
        }

        [HttpPost("~/connect/logout")]
        public IActionResult Logout()
        {
            return SignOut("ServerCookie", OpenIdConnectServerDefaults.AuthenticationScheme);
        }

    }
}
  • The sample directly uses the cookies middleware while your app likely uses ASP.NET Core Identity, which explains why the schemes differ. To solve your issue, it would help if you share the relevant code. – Kévin Chalet Nov 07 '16 at 18:07
  • I've attached Startup.cs content. – Christopher Schwarz Nov 08 '16 at 09:32
  • You should also post your authorization controller. – Kévin Chalet Nov 08 '16 at 13:30
  • My application should use also ASP.NET Core Identity because I also allow user to log in by my internal mechanism. Only Startup.cs differ from example. Maybe there are also few changes in my AccountController, but it's not important to this major problem. – Christopher Schwarz Nov 08 '16 at 14:11
  • Try to remove the custom cookies middleware registration (the `app.UseCookieAuthentication()` line) as one is already added by Identity when calling `app.UseIdentity()`. I'm also tempted to say your problem is simply caused by the fact you haven't completed the registration process, so you don't have an application cookie but an external cookie. It's definitely not an ASOS issue. – Kévin Chalet Nov 08 '16 at 14:19
  • When I've commented out this part of code, application redirected me to http://localhost:62892/Account/Login?ReturnUrl=%2Fconnect%2Fauthorize%3Fclient_id%3DmyClient%26redirect_uri%3Dhttp:%2F%2Flocalhost:62892%2Fcompetitions%2Fdashboard%26response_type%3Dtoken%26provider%3DFacebook but I've found one interesting thing, when I've added this two lines into services.AddIdentity() options.Cookies.ExternalCookie.AutomaticAuthenticate = true; options.Cookies.ExternalCookie.AutomaticChallenge = true; now my application works like a charm. Thanks for your help. – Christopher Schwarz Nov 08 '16 at 14:59

0 Answers0