7

The sample project https://github.com/xingyu217/Redis-nginx-aspnetcore-session nginx.config:

upstream study_server{
        server localhost:8011 weight=1;
        server localhost:8013 weight=1;
    #server localhost:8014 weight=1;
    #server 172.17.16.147:8012 weight=2;
    #server 172.17.16.147:8011 weight=2;
    }
    server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            #root   html;
            #index  index.html index.htm;
        proxy_pass http://study_server/;
            proxy_cookie_path ~*^/.* /;
        proxy_redirect off;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
        }

Get session id: ViewBag.seId= HttpContext.Session.Id;

Startup.cs:

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.AddDistributedMemoryCache();

            services.AddDistributedRedisCache(options =>
            {
                options.Configuration = Configuration.GetConnectionString("RedisConnection");
                options.InstanceName = "master";
            });
            services.AddSession(options =>
            {
                options.IdleTimeout = TimeSpan.FromSeconds(100);
                //options.Cookie.HttpOnly = true;
            });
            //services.AddSession();
            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.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                app.UseHsts();
            }

            //app.UseDeveloperExceptionPage();
            //app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseCookiePolicy();
            app.UseSession();
            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }

Access URL: localhost and refresh again and again, the session id will be always changing.

I checked keys in redis server, it always generate the new key when refresh page. (e.g.masterdef69307-fbd3-c6ed-91d2-009b2306f902)

If I just use a server (localhost:8011):

upstream study_server{
        server localhost:8011 weight=1;
        #server localhost:8013 weight=1;
    #server localhost:8014 weight=1;
    #server 172.17.16.147:8012 weight=2;
    #server 172.17.16.147:8011 weight=2;
    }

The session id won't be changed.

Anyone know it will be appreciated.

Thanks

starian chen-MSFT
  • 33,174
  • 2
  • 29
  • 53

5 Answers5

3

I actually cannot reproduce the problem on a mac with the configuration (2 local servers load balanced via nginx and connected to the same local redis distributed cache)

Anyways, if applicable, have you tried enabled ip_hash in nginx upstream module ?

From the sessions persistance section in nginx doc:

If there is the need to tie a client to a particular application server — in other words, make the client’s session “sticky” or “persistent” in terms of always trying to select a particular server — the ip-hash load balancing mechanism can be used.

upstream study_server{
    ip_hash;
    server localhost:8011 weight=1;
    server localhost:8013 weight=1;
    #server localhost:8014 weight=1;
    #server 172.17.16.147:8012 weight=2;
    #server 172.17.16.147:8011 weight=2;
}

If that's not solving your issue please have a look at similar issues on stack exchange:

Gomino
  • 12,127
  • 4
  • 40
  • 49
  • Thanks for your reply. The ip_hash could fix this issue, but seems always using a server for the same session. Anyway, it's ok for me now. I will mark your reply as answer if there isn't the better solution. Thanks very much. – starian chen-MSFT Jan 13 '20 at 11:59
  • Glad to help, you can always accept the current answer, and change your mind for a better one if it is the case ;) – Gomino Jan 14 '20 at 09:27
3

Solved it by using

var redis = ConnectionMultiplexer.Connect("<REDIS URI>");

services.AddDataProtection().PersistKeysToRedis(redis, "DataProtection-Keys");

Article:

https://onecodex.ch/blogsdotnetcoredistributed-redis-cache/

Necessary package: https://www.nuget.org/packages/Microsoft.AspNetCore.DataProtection.Redis/

starian chen-MSFT
  • 33,174
  • 2
  • 29
  • 53
2

If the CheckConsentNeeded property is assigned with true, the application will not set any non-essential cookie without user consent. This includes the session cookie, which is non-essential by default.

There are at least three solutions to your problem:

  1. You can set CheckConsentNeeded to false.
    ...

    options.CheckConsentNeeded = context => false;

    ...

This will disable the whole cookie consent feature.

  1. You can mark the session cookie as essential.
    ...

    services.AddSession(options =>
    {
        options.IdleTimeout = TimeSpan.FromSeconds(100);
        options.Cookie.IsEssential = true;
    });

    ...

This will allow the application to set the session cookie regardless of user's consent.

  1. You can leave everything as it is now. In this case the session will start only after the user clicks the "Accept" button on the cookie use policy popup.
Ivan Tsirulev
  • 2,751
  • 1
  • 19
  • 19
  • Thanks for your response. But I tried these options, the session id still be changed when refresh (sometimes the session ids are the same). This the fiddler trace https://github.com/xingyu217/fileshare. Could you please check it? Based on the log, it resets the session cookie in the response. – starian chen-MSFT Jan 10 '20 at 08:15
  • @starianchen-MSFT I cannot replicate the problem locally with the example project you've provided. Everything seems to be working fine after applying any of the mentioned solutions. Can you provide the exact version of the program that you use now? What does the log say when the application incorrectly starts a new session? Would it possible to temporary switch to local Redis (it's a long shot but still worth trying)? – Ivan Tsirulev Jan 10 '20 at 11:22
  • Hello lvan, I am using the latest version of the project https://github.com/xingyu217/Redis-nginx-aspnetcore-session. Also, I am using local Redis, but just specify local IP in appsettings.json. I also tried to use localhost:6379, same result. These are steps of my:1. Create website1 in local IIS and specify the path to published project folder. 2. Create another website2 with different port in local IIS and specify the same path. 3. Specify these address in local nginx and start/reload 4. Access localhost in Chrome/Edit/IE browsers 5. Press F5 to Refresh pages. 6. Check session value in page. – starian chen-MSFT Jan 10 '20 at 11:51
  • Are these steps the same as yours? Which log you mean (start a new session). – starian chen-MSFT Jan 10 '20 at 11:51
  • The important is as I said that if I just use a website (any one) in nginx, it works fine. server localhost:8011 weight=1; #server localhost:8013 weight=1; Just have issue if I use multiple websites in nginx. – starian chen-MSFT Jan 10 '20 at 11:54
  • I upload a record (gif) file in https://github.com/xingyu217/fileshare. Could you please check it? Thanks. – starian chen-MSFT Jan 10 '20 at 12:04
1

It seems that your sessions are not shared between your backends (servers).

You must check where your cookies are saved, and if this "location" is shared between your multiple servers.

If you use redis : Do they use the same redis ? the same namespace ? If it's local storage on disk, do they shared the same directory, etc.

Or as @Gomino said, your can use nginx sticky session to go to the same server each time (when your have a session). But this is not related to nginx configuration.

  • Thanks for your reply. I am using the same redis (on same machine). How do I check namespace? I can check all keys through redis client (keys *) and it will generate a new key likes master{guid} when refresh page. The ip_hash is configured in nginx.config, what do you mean it is not related to nginx configuration? – starian chen-MSFT Jan 15 '20 at 13:14
  • Nginx only "proxify" request to webserver, it's not him which manage session (in your case). Can you disable the proxy_cookie_path configuration, it seem to modify cookie path, so the cookie should not be find by the webserver, might be this. – Yann Schepens Jan 16 '20 at 10:50
  • Concerning redis namespaces, you can define a "namespace" into redis to separate keys, your two webservers must use the same, that's all. But if you tell us that there is only one session key even with the two server, the configuration must be good. – Yann Schepens Jan 16 '20 at 10:53
  • Tried to disable proxy_cookie_path, still same result. Thanks – starian chen-MSFT Jan 16 '20 at 12:58
  • I think your problem come from your session management into your code, but sorry i am .dot dev at all. You should try to follow the article purpose by @tematre. Sorry ;) – Yann Schepens Jan 16 '20 at 13:15
  • Thanks for your response. This article seems make sense https://onecodex.ch/blogsdotnetcoredistributed-redis-cache/ and have similar way, but still not work for me (services.AddDataProtection().SetApplicationName("My Application Name");). I tried with redis way, but there isn't PersistKeysToRedis method. – starian chen-MSFT Jan 16 '20 at 13:56
0

According to this article. https://medium.com/@LuizAdolphs/asp-net-core-distributed-session-with-redis-8ce8bacf26cd

The correct config Nginx is:

worker_processes 1;

events { worker_connections 1024; }

http {

    sendfile on;

    upstream app_server {
        server app_1:5000;
        server app_2:5050;
    }

    server {
        listen 80;

        location / {
            proxy_pass         http://app_server;
            proxy_redirect     off;
            proxy_set_header   Host $host;
            proxy_set_header   X-Real-IP $remote_addr;
            proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header   X-Forwarded-Host $server_name;
        }
    }
}

Please take a look at X-Forwarded-Host. Try to use this if it not solve the problem, it can be a problem in Redis configuration.

TemaTre
  • 1,422
  • 2
  • 12
  • 20
  • If it works, it's strange, because as the request come from the same origin, it should be good without. Except if session is attached "in config" with a specific host. – Yann Schepens Jan 16 '20 at 10:57