I'm using ASP.net Core 1, MVC 6. I am using SignInManager and UserManager, to authenticate a user in a web api application (MVC6 / C#) from another MVC application (the web api Logon method is actually called from a Jquery Ajax request).
In IE, I call the Login method and when successful, it gives me a Set-Cookie response with an ASP.net auth cookie. I can then see subsequent requests have the ASP.net auth cookie attached.
In chrome, the Set-Cookie directive is returned in the response, but subsequent requests do not have the cookie attached.
Why is this happening?
The only difference I can see is that in Chrome, there is a pre-flight OPTIONS request being sent, but I have handled that in the startup.cs file in the web api and am essentially ignoring it.
IE
My request to Login web api looks like this:
Accept */* Accept-Encoding gzip, deflate Accept-Language en-IE Cache-Control no-cache Connection Keep-Alive Content-Length 246 Content-Type application/x-www-form-urlencoded; charset=UTF-8 Cookie BeaeN4tYO5M=CfDJ8KMNkK4F2ylMlo1LFxNzxWLNDECVWfhxBYRQrw_MkNQBrVIwfO6FoMIMqg1PP-nZa8Dhp3IV1ZS1uXKpknUDYegiMlEvFaNG-wqUXErvQ5wkMMc_HBI88j-7bCbD2Q7P_B6fEQOQSTKHoL5sTcH0MoM DNT 1 Host localhost:44338 Referer https://localhost:44356/ Request POST /api/account/Login HTTP/1.1 User-Agent Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko X-ACL-Key 4A6F0007-95E7-4423-B786-6FBF981799FE
Response like this:
Key Value Response HTTP/1.1 200 OK Cache-Control no-cache Pragma no-cache Content-Type application/json; charset=utf-8 Expires -1 Server Kestrel Set-Cookie oAuthInterop=CfDJ8Asqo6qO2cNHlXpsdNLsuoQWhLxXcnaNkAMTB-VvpkMRIz2AiM_7feoIM29gza_zZz97qaE6TKdqK8y1jDPjDDyiiMdOMiuCmCoV5X4IQ9xtHvpGgmFoxOSiYFVeVOBbHsLx4BccL647F9sJ07M55zvjMx_7wrt32omhONH64vmc12P3nepwZjNSIFYfom1U0Z4r4EX_0tZjKRH7FrdvO0PI2iY5SMaKhCcBw1QXpQHSUxL6Hm-Wr8Q46gFAYoa6YffJV0Rx80FvJHmr1LMAA6PAF0dU_DzNdRVHdXm14t_nbfl-6xb6o7WQN259moUhkT1ZQ9CZsYwWvn7VBmpjfIXNJvIu0FDnRaHnNMrj3uN77_cAMdO3OcyCuy-CAKJ9c-0PxKToStb9juGSNa9ClpVQPADzpUxFqxZU029AXBPavXQK2Ezvy7YT4FwCkL8TEf5AnB5hfOZ5YCBlqD30n2heMdHDbXRHpxeaQB4aoY_6uSpJ3cPazBDsbvGi4fV2-0g5NvoTGgJUXa5p4UntRmuiJ2tZHbMmEjXzf-GV6QtTFIhseKsS3n6TMX68yqQOhYOzxvHdJXPjYxvjmm6-vJw5w2FDgiEXoQJQ7qaSmGzRwOA_cE4VBV_RhzrZELmp3A; path=/; secure; httponly X-SourceFiles =?UTF-8?B?QzpcVXNlcnNcUm9iZXJ0XERlc2t0b3BcSEJFIE1hbmFnZXJcTUFJTlxCbHVlem9uZSBXZWJBcGlcc3JjXEJ6LkFwcGxpY2F0aW9uXEJ6LkFwcGxpY2F0aW9uLkFwaVx3d3dyb290XGFwaVxhY2NvdW50XExvZ2lu?= X-Powered-By ASP.NET Access-Control-Allow-Methods GET,PUT,POST,DELETE Access-Control-Allow-Headers Content-Type,x-xsrf-token,X-ACL-Key Date Fri, 06 May 2016 14:23:22 GMT Content-Length 16
Subsequent test web api call (IsLoggedIn):
Key Value Request GET /api/account/IsLoggedIn HTTP/1.1 X-ACL-Key 4A6F0007-95E7-4423-B786-6FBF981799FE Accept */* Refererhttps://localhost:44356/ Accept-Language en-IE Accept-Encoding gzip, deflate User-Agent Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko Host localhost:44338 DNT 1 Connection Keep-Alive Cache-Control no-cache Cookie BeaeN4tYO5M=CfDJ8KMNkK4F2ylMlo1LFxNzxWLNDECVWfhxBYRQrw_MkNQBrVIwfO6FoMIMqg1PP-nZa8Dhp3IV1ZS1uXKpknUDYegiMlEvFaNG-wqUXErvQ5wkMMc_HBI88j-7bCbD2Q7P_B6fEQOQSTKHoL5sTcH0MoM; oAuthInterop=CfDJ8Asqo6qO2cNHlXpsdNLsuoQWhLxXcnaNkAMTB-VvpkMRIz2AiM_7feoIM29gza_zZz97qaE6TKdqK8y1jDPjDDyiiMdOMiuCmCoV5X4IQ9xtHvpGgmFoxOSiYFVeVOBbHsLx4BccL647F9sJ07M55zvjMx_7wrt32omhONH64vmc12P3nepwZjNSIFYfom1U0Z4r4EX_0tZjKRH7FrdvO0PI2iY5SMaKhCcBw1QXpQHSUxL6Hm-Wr8Q46gFAYoa6YffJV0Rx80FvJHmr1LMAA6PAF0dU_DzNdRVHdXm14t_nbfl-6xb6o7WQN259moUhkT1ZQ9CZsYwWvn7VBmpjfIXNJvIu0FDnRaHnNMrj3uN77_cAMdO3OcyCuy-CAKJ9c-0PxKToStb9juGSNa9ClpVQPADzpUxFqxZU029AXBPavXQK2Ezvy7YT4FwCkL8TEf5AnB5hfOZ5YCBlqD30n2heMdHDbXRHpxeaQB4aoY_6uSpJ3cPazBDsbvGi4fV2-0g5NvoTGgJUXa5p4UntRmuiJ2tZHbMmEjXzf-GV6QtTFIhseKsS3n6TMX68yqQOhYOzxvHdJXPjYxvjmm6-vJw5w2FDgiEXoQJQ7qaSmGzRwOA_cE4VBV_RhzrZELmp3A
Response like this:
Key Value Response HTTP/1.1 200 OK Content-Type application/json; charset=utf-8 Server Kestrel X-SourceFiles =?UTF-8?B?QzpcVXNlcnNcUm9iZXJ0XERlc2t0b3BcSEJFIE1hbmFnZXJcTUFJTlxCbHVlem9uZSBXZWJBcGlcc3JjXEJ6LkFwcGxpY2F0aW9uXEJ6LkFwcGxpY2F0aW9uLkFwaVx3d3dyb290XGFwaVxhY2NvdW50XElzTG9nZ2VkSW4=?= X-Powered-By ASP.NET Access-Control-Allow-Methods GET,PUT,POST,DELETE Access-Control-Allow-Headers Content-Type,x-xsrf-token,X-ACL-Key Date Fri, 06 May 2016 14:23:22 GMT Content-Length 68
CHROME
My request to Login web api looks like this:
POST /api/account/Login HTTP/1.1 Host: localhost:44338 Connection: keep-alive Content-Length: 246 Accept: */* Origin:https://localhost:44356 Content-Type: application/x-www-form-urlencoded; charset=UTF-8 User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36 X-ACL-Key: 4A6F0007-95E7-4423-B786-6FBF981799FE Referer: https://localhost:44356/ Accept-Encoding: gzip, deflate Accept-Language: en-GB,en-US;q=0.8,en;q=0.6
Response like this:
HTTP/1.1 200 OK Cache-Control: no-cache Pragma: no-cache Content-Type: application/json; charset=utf-8 Expires: -1 Vary: Origin Server: Kestrel Set-Cookie: oAuthInterop=CfDJ8Asqo6qO2cNHlXpsdNLsuoRvlRjfUBWrkt3W3NzBJIoFYA6DcQivnfYmZV2O5xuiqpd75oRjZ-JeHBcjiOK0HoFJQ9f61RyJ2HDeuCNmQk0H-pA3Lzs5ft_F49dpQt0kFn3_-FzEh5-NScCbY4N6TiuYlWY4VSoKsdJJ91k7Z4LQO-0Wm3cZ6HfX0E6pLzGG4lWaZGuV-gOsVCRygR5nv_O_YpWwfaLsT_51aX6fNXVSotU6MECEkFdfWseqOGyYVj7KJrxY2mPwksE0XGACs12TnmfJzCABrzd06FnTPy3RuqJF2IWOobX6ZAHGMoAVFR07mhy9gMPyaHQ12RKmhBhZSXE-Yi3BHow2ER9d2Niligx7JjwYR7UfHFHWJdoYzewLRkZZGE5pw67O710hYyA2UCM2ODB9l9x-WDQ1A_3xjxu2Mrkp0lrF0V-h3y6V2gzEP9RyQAjDISEEZQqvb-GzfZrsRzzQcMn0TMhq5_LUKkX3AScSGRiarBzZ2O9Af3jzwTmN1BciJknJwMKRefq_zrXH7kymCD1kJM89aGkswqp2bycMQjlsjqg5k8EEhv8u1kLA7hA9NyE2ZaamB1PAWYz4NXi3Agccgw83nFi4bs6VE8ZLnyZFEwxdyEGyvQ; path=/; secure; httponly Access-Control-Allow-Origin: https://localhost:44356 Access-Control-Allow-Credentials: true X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNcUm9iZXJ0XERlc2t0b3BcSEJFIE1hbmFnZXJcTUFJTlxCbHVlem9uZSBXZWJBcGlcc3JjXEJ6LkFwcGxpY2F0aW9uXEJ6LkFwcGxpY2F0aW9uLkFwaVx3d3dyb290XGFwaVxhY2NvdW50XExvZ2lu?= X-Powered-By: ASP.NET Access-Control-Allow-Methods: GET,PUT,POST,DELETE Access-Control-Allow-Headers: Content-Type,x-xsrf-token,X-ACL-Key Date: Fri, 06 May 2016 12:59:36 GMT Content-Length: 16
Subsequent test web api call (IsLoggedIn):
GET /api/account/IsLoggedIn HTTP/1.1 Host: localhost:44338 Connection: keep-alive Accept: */* Origin:https://localhost:44356 User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36 X-ACL-Key: 4A6F0007-95E7-4423-B786-6FBF981799FE Referer:https://localhost:44356/ Accept-Encoding: gzip, deflate, sdch Accept-Language: en-GB,en-US;q=0.8,en;q=0.6
Response like this:
HTTP/1.1 401 Unauthorized Content-Length: 0 Content-Type: text/plain; charset=utf-8 Server: Kestrel X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNcUm9iZXJ0XERlc2t0b3BcSEJFIE1hbmFnZXJcTUFJTlxCbHVlem9uZSBXZWJBcGlcc3JjXEJ6LkFwcGxpY2F0aW9uXEJ6LkFwcGxpY2F0aW9uLkFwaVx3d3dyb290XGFwaVxhY2NvdW50XElzTG9nZ2VkSW4=?= X-Powered-By: ASP.NET Access-Control-Allow-Methods: GET,PUT,POST,DELETE Access-Control-Allow-Headers: Content-Type,x-xsrf-token,X-ACL-Key Date: Fri, 06 May 2016 12:59:43 GMT
My web api controller code looks like this:
[Authorize] [EnableCors("AllowAll")] [Route("api/[controller]")] public class AccountController : Controller { private readonly UserManager<ApplicationUser> _userManager; private readonly SignInManager<ApplicationUser> _signInManager; public AccountController(UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager) { _userManager = userManager; _signInManager = signInManager; } [HttpPost("login")] [AllowAnonymous] public async Task<IActionResult> Login(UserLogin model) { if (ModelState.IsValid) { var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false); if (result.Succeeded) { return Json(new { success = true }); } if (result.RequiresTwoFactor) { return Json(new { success = false, errType = 1 }); } if (result.IsLockedOut) { return Json(new { success = false, errType = 2 }); } else { ModelState.AddModelError(string.Empty, "Invalid login attempt."); return Json(new { success = false, errType = 3 }); } } return Json(new { success = false, errType = 0 }); } [HttpGet("IsLoggedIn")] public IActionResult IsLoggedIn() { return Json(new { loggedon = (HttpContext.User.Identity.Name != null && HttpContext.User.Identity.IsAuthenticated), isauthenticated = HttpContext.User.Identity.IsAuthenticated, username = HttpContext.User.Identity.Name }); } }
Startup.cs for my web api looks like this:
public class Startup { public static int SessionLength { get; private set; } private string Connection; public Startup(IHostingEnvironment env) { // Set up configuration sources. var builder = new ConfigurationBuilder() .AddJsonFile("appsettings.json") .AddEnvironmentVariables(); Configuration = builder.Build(); SessionLength = 30; } public IConfigurationRoot Configuration { get; set; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { // Get the configured connection string. Connection = Configuration["Data:DefaultConnection:ConnectionString"]; var userStore = new CustomUserStore(); var roleStore = new CustomRoleStore(); var userPrincipalFactory = new CustomUserPrincipalFactory(); services.AddInstance<IUserStore<ApplicationUser>>(userStore); services.AddInstance<IRoleStore<ApplicationRole>>(roleStore); services.AddInstance<IUserClaimsPrincipalFactory<ApplicationUser>>(userPrincipalFactory); services.AddIdentity<ApplicationUser, ApplicationRole>(options => { options.Cookies.ApplicationCookie.Events = new CookieAuthenticationEvents() { OnRedirectToAccessDenied = ctx => { if (ctx.Response.StatusCode == (int)HttpStatusCode.Unauthorized || ctx.Response.StatusCode == (int)HttpStatusCode.Forbidden) { return Task.FromResult<object>(null); } ctx.Response.Redirect(ctx.RedirectUri); return Task.FromResult<object>(null); }, OnRedirectToLogin = ctx => { if (ctx.Response.StatusCode == (int)HttpStatusCode.Unauthorized || ctx.Response.StatusCode == (int)HttpStatusCode.Forbidden) { return Task.FromResult<object>(null); } ctx.Response.Redirect(ctx.RedirectUri); return Task.FromResult<object>(null); } }; //options.Cookies.ApplicationCookie.CookieHttpOnly = false; options.Cookies.ApplicationCookieAuthenticationScheme = "ApplicationCookie"; options.Cookies.ApplicationCookie.AuthenticationScheme = "ApplicationCookie"; options.Cookies.ApplicationCookie.CookieName = "oAuthInterop"; options.Cookies.ApplicationCookie.AutomaticChallenge = true; options.Cookies.ApplicationCookie.AutomaticAuthenticate = true; options.Cookies.ApplicationCookie.DataProtectionProvider = new DataProtectionProvider(new DirectoryInfo("d:\\development\\artefacts"), configure => { configure.SetApplicationName("TestAuthApp"); //configure.ProtectKeysWithCertificate("thumbprint"); }); options.Cookies.ApplicationCookie.ExpireTimeSpan = TimeSpan.FromMinutes(SessionLength); }).AddDefaultTokenProviders(); // Add framework services. services.AddMvc(); // Add cross site calls. //TODO: implement with better security instead of allowing everything through. services.AddCors(options => options.AddPolicy("AllowAll", p => p.AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader().AllowCredentials())); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(Configuration.GetSection("Logging")); loggerFactory.AddDebug(); app.UseIISPlatformHandler(options => options.AuthenticationDescriptions.Clear()); app.UseStaticFiles(); app.UseIdentity(); app.UseMvc(); } }