dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.37k stars 9.99k forks source link

Cookie/Session lost at chrome #15136

Closed magicdict closed 4 years ago

magicdict commented 5 years ago

If you believe you have an issue that affects the security of the platform please do NOT create an issue and instead email your issue details to secure@microsoft.com. Your report may be eligible for our bug bounty but ONLY if it is reported through email.

Describe the bug

WebApi lost session because chrome lost cookie.

To Reproduce

using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace ScsErpUI
{
    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.AddControllers();

            services.AddDistributedMemoryCache();

            services.AddSession(options =>
            {
                // Set a short timeout for easy testing.
                options.IdleTimeout = TimeSpan.FromSeconds(600);
                options.Cookie.HttpOnly = true;
                // Make the session cookie essential
                options.Cookie.IsEssential = true;
            });

            services.AddMvc( MvcOptions => MvcOptions.EnableEndpointRouting = false );
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseCors(builder => builder.WithOrigins("*").AllowAnyHeader().AllowAnyMethod());
            app.UseSession();
            app.UseMvc();
        }
    }
}

Expected behavior

When I Call webapi from Angular by http.post method, IE get the cookie and the session works good.

But I chrome can't get the cookie...

Screenshots

image

image image

(Allow website to read and save cookie) image

Additional context

The Webapi works good at NetCore3.0 Preivew version, Current Envirment: NetCore3.0 IE11 Chrome:lastest

magicdict commented 5 years ago

IE11 On Win7 works well, IE11 On Win10 does not Well, The first Page get the cookie,but the second page does not send the session to server.

magicdict commented 5 years ago
 [ApiController]
    [Route("[controller]")]
    public class AccountController : ControllerBase
    {
       [HttpPost("Login")]
        public string Login(dynamic data)
        {
            UserInfo userinfo = JsonConvert.DeserializeObject(data.ToString(), typeof(UserInfo));
            var client = new HttpClient();
            client.BaseAddress = new Uri(WebApiHelper.urlbase_Dev);
            HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "/scserp/login.do");
            var parm = new Dictionary<string, string>();
            parm.Add("txtUid", userinfo.txtUid);
            parm.Add("pswPsw", userinfo.pswPsw);
            request.Content = new FormUrlEncodedContent(parm);
            var postTask = client.PostAsync(request.RequestUri, request.Content);
            var response = postTask.Result;
            var r = response.Content.ReadAsStringAsync().Result;
            //从Cookie中获取JSESSIONID
            var JSE = response.Headers.GetValues("Set-Cookie").First();
            //保存在服务器的Session中
            base.HttpContext.Session.SetString("JSE", JSE);
            //request.Headers.Add("Cookie", JSE); 
            var strJson = WebApiHelper.ConvertXml2Json(r.ToString());
            dynamic jsonInfoRoot = JsonConvert.DeserializeObject(strJson);
            InfoRoot infoRoot = new InfoRoot(jsonInfoRoot);
            if (infoRoot.infoUser.Authcd !=null){
                base.HttpContext.Session.SetString("Authcd", infoRoot.infoUser.Authcd);
                base.HttpContext.Session.SetString("Uid", infoRoot.infoUser.Uid);
            }
            return strJson;
        }

 [HttpPost("ChangePassword")]
        public string ChangePassword(dynamic data)
        {
            var JSE = base.HttpContext.Session.GetString("JSE");
            var client = new HttpClient();
            client.BaseAddress = new Uri(WebApiHelper.urlbase_Dev);
            client.DefaultRequestHeaders.Add("Cookie", JSE);
            HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "/scserp/chgpsw.do");
            var parm = new Dictionary<string, string>();
            parm.Add("txtPswOld", data.txtPswOld.ToString());
            parm.Add("txtPswNew", data.txtPswNew.ToString());
            parm.Add("txtPswCom", data.txtPswCom.ToString());
            parm.Add("hidServiceid","02002"); 
            parm.Add("hidUid", base.HttpContext.Session.GetString("Uid"));
            parm.Add("hidAuthcd", base.HttpContext.Session.GetString("Authcd"));
            request.Content = new FormUrlEncodedContent(parm);
            var postTask = client.PostAsync(request.RequestUri, request.Content);
            var response = postTask.Result;
            var r = response.Content.ReadAsStringAsync().Result;
            var strJson = WebApiHelper.ConvertXml2Json(r.ToString());
            return strJson;
        }
javiercn commented 5 years ago

@magicdict Thanks for contacting us.

@blowdart Can this be caused by the chrome change for cookies?

Tratcher commented 5 years ago

Session should NOT be used for login, use CookieAuthentication instead.

@blowdart Can this be caused by the chrome change for cookies?

Those aren't live until Feb.

analogrelay commented 5 years ago

@magicdict can you provide a runnable sample application that reproduces the problem? That will allow us to properly inspect all the code and get an idea of what the problem may be

As @Tratcher said, Session is not suitable for authentication. If you want to do custom authentication using cookies, we have documentation on how to do that: https://docs.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-3.0

magicdict commented 4 years ago

@anurse : I will give you a demo asap.

magicdict commented 4 years ago

@anurse : The demo source [Chrome] Set Session image Get Session image

[IE11] Set Session image

Get Session image

[Source Folder] WebApi: WebApi Source dotnet run UI:Angular Source (node_modules is deleted!!!) 'ng serve' to start angular app (localhost:4200)

AspNetCoreIssue.zip

analogrelay commented 4 years ago

I think I see the problem. There are two apps here, a WebApi and an Angular UI. That means the Get/Set Session requests are cross-domain. Modern browsers such as Chrome (and Edge and Firefox) won't send cookies on cross-domain requests unless you explicitly enable it on both the client and the server. See the "Requests with credentials" section in the Mozilla documentation on CORS.

You need to set withCredentials when make your http request, and similarly will need to add .AllowCredentials in your CORS policy. Note that when you do that, *you must not use the origin ``**. See our documentation on CORS for more info

magicdict commented 4 years ago

@anurse Thank you for your answer.

            services.AddCors(options =>
            {
                options.AddPolicy("AllowCredentials",
                builder =>
                {
                    builder.WithOrigins("*")
                        .AllowCredentials();
                });
            });

I add AllowCredentials to my code,but there is a exception.

            services.AddCors(options =>
            {
                options.AddPolicy("AllowCredentials",
                builder =>
                {
                    builder.AllowCredentials();
                });
            });

When I delete WithOrigins("*") WebApi can run.

httpRequest<T>(controllerName: string, params: any = {}): Promise<T> {
    return this.http.post(
      "http://localhost:5000" + controllerName,
      params, 
      {
        withCredentials: true
      } 
    )
      .toPromise()
      .then(response => {
        return response as T;
      })
      .catch(this.handleError);
  }

I set withCredentials to true,but the demo does not work...

Access to XMLHttpRequest at 'http://localhost:5000/session/setsession' from origin 'http://localhost:4200' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

Can you tell me how to modify startup.cs ,thank you.

BrennanConroy commented 4 years ago

builder.WithOrigins("http://localhost:4200").AllowCredentials()

magicdict commented 4 years ago

@BrennanConroy demo does not work... Access to XMLHttpRequest at 'http://localhost:5000/session/setsession' from origin 'http://localhost:4200' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

I don't know the problem at server(NetCore) or client(angular)

magicdict commented 4 years ago

@anurse @BrennanConroy The Issue is solved with the code。Thank you。

【Serve】

using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace WebApi
{
    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.AddControllers();

            services.AddDistributedMemoryCache();

            services.AddSession(options =>
            {
                // Set a short timeout for easy testing.
                options.IdleTimeout = TimeSpan.FromSeconds(600);
                options.Cookie.HttpOnly = true;
                // Make the session cookie essential
                options.Cookie.IsEssential = true;
            });

            services.AddCors(options =>
            {
                options.AddPolicy("AllowCredentials",
                builder =>
                {
                    builder.WithOrigins("http://localhost:4200").AllowCredentials().AllowAnyHeader().AllowAnyMethod();
                });
            });

            services.AddMvc(MvcOptions =>
            {
                MvcOptions.EnableEndpointRouting = false;
            });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseCors("AllowCredentials");
            app.UseSession();
            app.UseMvc();
        }
    }
}

【Angular】

httpRequest<T>(controllerName: string, params: any = {}): Promise<T> {
    return this.http.post(
      "http://localhost:5000" + controllerName,
      params, 
      {
        withCredentials: true
      } 
    )
      .toPromise()
      .then(response => {
        return response as T;
      })
      .catch(this.handleError);
  }