ruby-on-railsnginxdeviseerrbit

errbit container running http, nginx reverse proxy redirecting https to http, cannot login


I have a very simple setup using an Errbit container running on http and an nginx reverse proxy that takes https and redirects it to the Errbit container using http (config file see below). I can not login when accessing the Errbit in a browser using https, but I can using http. Errbit uses Devise, and as far as I can see from the logs this is a problem with Devise.

So here's my nginx config:

server {
    listen 80 default_server;

    server_name _;

    access_log  /var/log/nginx/errbit.access.log  main;
    error_log  /var/log/nginx/errbit.error.log  warn;
    location / {

        proxy_pass http://errbit:8080;
        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-Proto $scheme;
    }

    location ~ /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }
}
server {
    listen 443 ssl;
    http2 on;
    ssl_certificate     /etc/letsencrypt/live/mydomain.de/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mydomain.de/privkey.pem;
    server_name mydomain.de;
    root /var/www/html;
    index index.html;

    access_log  /var/log/nginx/errbit.access.log  main;
    error_log  /var/log/nginx/errbit.error.log  warn;

    location ~/.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        proxy_pass http://errbit:8080;
        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_http_version 1.1;
        proxy_set_header X-XSRF-TOKEN $http_x_xsrf_token;
    }
}

The Errbit container runs with a puma config unchanged from its repo. Furthermore, Errbit has not got its ERRBIT_ENFORCE_SSL setting set. The idea is that the Errbit container doesn't need to know anything about SSL, nginx handles it.

As things are now, I can go to https://mydomain.de and the login page from Errbit will be displayed, so the redirection works. I can also go to http://mydomain.de and the same page will be displayed. I can log in successfully from the http page. However, if I try to log in from the https page, I get the flash notification "You need to sign in or sign up before continuing." (which is the error text for "401 Unauthorized") and get redirected to the login page again. This is the server log:

Started GET "/users/sign_in" for 172.18.0.4 at 2024-08-07 10:30:06 +0000
Processing by Devise::SessionsController#new as */*
  Rendering devise/sessions/new.html.haml within layouts/application
  Rendered devise/sessions/new.html.haml within layouts/application (12.9ms)
  Rendered shared/_session.html.haml (0.1ms)
  Rendered shared/_flash_messages.html.haml (0.1ms)
Completed 200 OK in 17ms (Views: 15.8ms)
Started GET "/users/sign_in" for 172.18.0.4 at 2024-08-07 10:30:36 +0000
Processing by Devise::SessionsController#new as */*
  Rendering devise/sessions/new.html.haml within layouts/application
  Rendered devise/sessions/new.html.haml within layouts/application (3.2ms)
Started POST "/users/sign_in" for 87.145.10.148 at 2024-08-07 10:30:36 +0000
Processing by Devise::SessionsController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"vy9n792gHd0eeEKOo2sPVZUomk0U+bDWU6ahszuLS7P3XVfA7V6/bjNpN6EbJ0svEdqQF+ltWUp9vHyBk1ysLw==", "user"=>{"email"=>"marion.dickten@dcs-fuerth.de", "password"=>"[FILTERED]", "remember_me"=>"0"}}
Can't verify CSRF token authenticity.
  Rendered shared/_session.html.haml (0.1ms)
  Rendered shared/_flash_messages.html.haml (0.1ms)
Completed 200 OK in 11ms (Views: 10.2ms)
MONGODB | mongo:27017 req:67 conn:1:1 sconn:98 | errbit.find | STARTED | {"find"=>"users", "filter"=>{"email"=>"marion.dickten@dcs-fuerth.de"}, "limit"=>1, "sort"=>{"_id"=>1}, "singleBatch"=>true, "$db"=>"errbit", "lsid"=>{"id"=><BSON::Binary:0x77140 type=uuid data=0x5a1538b26430493e...>}}
MONGODB | mongo:27017 req:67 | errbit.find | SUCCEEDED | 0.001s
MONGODB | mongo:27017 req:68 conn:1:1 sconn:98 | errbit.update | STARTED | {"update"=>"users", "ordered"=>true, "updates"=>[{"q"=>{"_id"=>BSON::ObjectId('665ef40c31fe4e000bef3991')}, "u"=>{"$set"=>{"last_sign_in_at"=>2024-08-07 10:28:58.329 UTC, "current_sign_in_at"=>2024-08-07 10:30:36.352921117 UTC, "sign_in_count"=>19, "u...
MONGODB | mongo:27017 req:68 | errbit.update | SUCCEEDED | 0.001s
Redirected to http://mydomain.de/
Completed 302 Found in 156ms
Started GET "/" for 87.145.10.148 at 2024-08-07 10:30:36 +0000
Processing by AppsController#index as HTML
Completed 401 Unauthorized in 1ms
Started GET "/users/sign_in" for 87.145.10.148 at 2024-08-07 10:30:36 +0000
Processing by Devise::SessionsController#new as HTML
  Rendering devise/sessions/new.html.haml within layouts/application
  Rendered devise/sessions/new.html.haml within layouts/application (3.0ms)
  Rendered shared/_session.html.haml (0.1ms)
  Rendered shared/_flash_messages.html.haml (0.1ms)
Completed 200 OK in 7ms (Views: 5.2ms)

As I can see it, in spite of the proxy_set_header X-XSRF-TOKEN line the CSRF token gets lost somehow, and furthermore the AppsController decides I'm unauthorized. But this only happens if I try to login from the https page.

I'd like to understand what is happening here and how to fix it (since I'd like to close off the possibility to access my Errbit via http).


Solution

  • Regarding the CSRF devise Documents state:

    ...note that protect_from_forgery is no longer prepended to the before_action chain, so if you have set authenticate_user before protect_from_forgery, your request will result in "Can't verify CSRF token authenticity." To resolve this, either change the order in which you call them, or use protect_from_forgery prepend: true"

    In regards to the https login issue you need to add the same X-Forward-Proto header you have for http to your https location directive in your nginx config:

    proxy_set_header X-Forwarded-Proto $scheme
    

    My general understanding of this is:

    Your are passing the request through to the proxy as http, even though the original client request was https. Without this header the receiving server is not aware of the client protocol only the protocol used through the proxy.

    What this does is forward on the original protocol used by the client to the server, in this case making the server aware that the request was made over https, which allows the server to recognize and respond appropriately as if the request was made directly over https.