ory kratos csrf cookie在nginx ssl后面没有被发送。

huangapple go评论86阅读模式
英文:

ory kratos csrf cookie not being sent behind nginx ssl

问题

我正在使用Docker中的Go和ory kratos,在我的本地机器上的localhost上一切正常工作。

认证工作正常,所有的cookie都被发送和设置,我可以从SPA调用我的后端并进行身份验证。

问题是,在nginxssl后面的生产服务器上,显然只有一个cookie没有从我的js客户端发送(只有ory_kratos_session被发送,而没有xxx_csrf_token cookie),并且它在下面的函数中由于缺少cookie而失败。

它使用官方的go sdk:kratos-client-go

Go AuthRequired中间件

func ExtractKratosCookiesFromRequest(r *http.Request) (csrf, session *http.Cookie, cookieHeader string) {
	cookieHeader = r.Header.Get("Cookie")

	cookies := r.Cookies()
	for _, c := range cookies {
		if c != nil {
			if ok := strings.HasSuffix(c.Name, "csrf_token"); ok {
				csrf = c
			}
		}
	}

	sessionCookie, _ := r.Cookie("ory_kratos_session")
	if sessionCookie != nil {
		session = sessionCookie
	}

	return
}

func AuthRequired(w http.ResponseWriter, r *http.Request) error {
	csrfCookie, sessionCookie, cookieHeader := ExtractKratosCookiesFromRequest(r)
	if (csrfCookie == nil || sessionCookie == nil) || (csrfCookie.Value == "" || sessionCookie.Value == "") {
		return errors.New("Cookie missing")
	}

	req := kratos.PublicApi.Whoami(r.Context()).Cookie(cookieHeader)
	kratosSession, _, err := req.Execute()
	if err != nil {
		return errors.New("Whoami error")
	}
	
	return nil
}

我的js http客户端有选项:credentials: 'include'

在开发工具面板中,我只看到一个cookie(ory_kratos_session)在注册/登录后。

所以问题出在请求只发送了ory_kratos_session而没有发送xxx_csrf_token cookie(在kratos的--dev模式下在localhost上工作,并且在开发工具面板中可见)。

请求信息

常规:

请求URL:https://example.com/api/v1/users/1/donations
请求方法:GET
状态码:401 未授权
远程地址:217.163.23.144:443
引荐策略:strict-origin-when-cross-origin

请求头:

accept: application/json
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
content-type: application/json; charset=UTF-8
Cookie: ory_kratos_session=MTYyMjA0NjEyMHxEdi1CQkFFQ180SUFBUkFCRUFBQVJfLUNBQUVHYzNSeWFXNW5EQThBRFhObGMzTnBiMjVmZEc5clpXNEdjM1J5YVc1bkRDSUFJRFo0Y2tKUFNFUmxZWFpsV21kaFdVbFZjMFU0VVZwcFkxbDNPRFpoY1ZOeXyInl242jY9c2FDQmykJrjLTNLg-sPFv2y04Qfl3uDfpA==
Host: example.com
Referer: https://example.com/dashboard/donations
sec-ch-ua: " Not;A Brand";v="99", "Google Chrome";v="91", "Chromium";v="91"
sec-ch-ua-mobile: ?0
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36

响应头:

Connection: keep-alive
Content-Length: 175
Content-Type: application/json
Date: Wed, 26 May 2021 17:12:27 GMT
Server: nginx/1.18.0 (Ubuntu)
Vary: Origin

docker-compose.yml

version: "3.8"

services:
  # --------------------------------------------------------------------------------
  api-server:
    build:
      context: .
      dockerfile: ./dockerfiles/app.dockerfile
    container_name: api-server
    restart: always
    volumes:
      - ./:/app
    ports:
      - 3001:3001
    networks:
      - intranet
    depends_on:
      - postgresd
  # --------------------------------------------------------------------------------
  postgresd:
    image: postgres:13.3-alpine
    container_name: postgresd
    restart: always
    environment:
      - POSTGRES_DB=test
      - POSTGRES_USER=test
      - POSTGRES_PASSWORD=test
    volumes:
      - postgres-data:/var/lib/postgresql/data
    ports:
      - 5432:5432
    networks:
      - intranet
  # --------------------------------------------------------------------------------
  kratos-migrate:
    image: oryd/kratos:v0.6.2-alpha.1
    container_name: kratos-migrate
    restart: on-failure
    environment:
      - DSN=postgres://test:test@postgresd:5432/test?sslmode=disable&max_conns=20&max_idle_conns=4
    volumes:
      - type: bind
        source: ./kratos/config
        target: /etc/config/kratos
    command:
      [
        "migrate",
        "sql",
        "--read-from-env",
        "--config",
        "/etc/config/kratos/kratos.yml",
        "--yes",
      ]
    networks:
      - intranet
    depends_on:
      - postgresd
  # --------------------------------------------------------------------------------
  kratos:
    image: oryd/kratos:v0.6.2-alpha.1
    container_name: kratos
    restart: unless-stopped
    environment:
      - DSN=postgres://test:test@postgresd:5432/test?sslmode=disable&max_conns=20&max_idle_conns=4
    command: ["serve", "--config", "/etc/config/kratos/kratos.yml"]
    volumes:
      - type: bind
        source: ./kratos/config
        target: /etc/config/kratos
    ports:
      - 4433:4433
      - 4434:4434
    networks:
      - intranet
    depends_on:
      - postgress
      - kratos-migrate
  # --------------------------------------------------------------------------------

volumes:
  postgres-data:

networks:
  intranet:
    driver: bridge

kratos.yml

version: v0.6.2-alpha.1

dsn: postgres://test:test@postgresd:5432/test?sslmode=disable&max_conns=20&max_idle_conns=4

serve:
  public:
    base_url: https://example.com/kratos/
    cors:
      enabled: true
      debug: true
      allow_credentials: true
      options_passthrough: true
      allowed_origins:
        - https://example.com
      allowed_methods:
        - POST
        - GET
        - PUT
        - PATCH
        - DELETE
        - OPTIONS
      allowed_headers:
        - Authorization
        - Cookie
        - Origin
        - X-Session-Token
      exposed_headers:
        - Content-Type
        - Set-Cookie
  admin:
    base_url: https://example.com/kratos/

selfservice:
  default_browser_return_url: https://example.com
  whitelisted_return_urls:
    - https://example.com
    - https://example.com/dashboard
    - https://example.com/auth/login
  methods:
    password:
      enabled: true
    oidc:
      enabled: false
    link:
      enabled: true
    profile:
      enabled: true
  flows:
    error:
      ui_url: https://example.com/error
    settings:
      ui_url: https://example.com/dashboard/profile
      privileged_session_max_age: 15m
    recovery:
      enabled: true
      ui_url: https://example.com/auth/recovery
      after:
        default_browser_return_url: https://example.com/auth/login
    verification:
      enabled: true
      ui_url: https://example.com/auth/verification
      after:
        default_browser_return_url: https://example.com
    logout:
      after:
        default_browser_return_url: https://example.com
    login:
      ui_url: https://example.com/auth/login
      lifespan: 10m
    registration:
      lifespan: 10m
      ui_url: https://example.com/auth/registration
      after:
        password:
          hooks:
            - hook: session
          default_browser_return_url: https://example.com/auth/login
        default_browser_return_url: https://example.com/auth/login
        oidc:
          hooks:
            - hook: session

secrets:
  cookie:
    - fdwfhgwjfgwf9286f24tf29ft

session:
  lifespan: 24h
  cookie:
    domain: example.com # i tried also with http:// and https://
    same_site: Lax

hashers:
  argon2:
    parallelism: 1
    memory: 128MB
    iterations: 1
    salt_length: 16
    key_length: 16

identity:
  default_schema_url: file:///etc/config/kratos/identity.schema.json

courier:
  smtp:
    connection_uri: smtp://user:pwd@smtp.mailtrap.io:2525
    from_name: test
    from_address: office@test.com

watch-courier: true

log:
  level: debug
  format: text
  leak_sensitive_values: true

我的Go REST API有以下CORS选项:

ALLOWED_ORIGINS=https://example.com
ALLOWED_METHODS=GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS
ALLOWED_HEADERS=Content-Type,Authorization,Cookie,Origin,X-Session-Token,X-CSRF-Token,Vary
EXPOSED_HEADERS=Content-Type,Authorization,Content-Length,Cache-Control,Content-Language,Content-Range,Set-Cookie,Pragma,Expires,Last-Modified,X-Session-Token,X-CSRF-Token
MAX_AGE=86400
ALLOW_CREDENTIALS=true

nginx默认配置

upstream go-api {
    server 127.0.0.1:3001;
}

upstream kratos {
    server 127.0.0.1:4433;
}

upstream kratos-admin {
    server 127.0.0.1:4434;
}

server {
        server_name example.com www.example.com;

        location / {
                root /var/www/website;
                try_files $uri $uri/ /index.html;
        }
  
        location /api/ {
                 proxy_pass http://go-api;
                 proxy_http_version 1.1;
                 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-Port $server_port;
                 proxy_set_header x-forwarded-proto $scheme;
                 proxy_set_header Upgrade $http_upgrade;
                 proxy_set_header Connection 'upgrade';
                 proxy_cache_bypass $http_upgrade;
        }

        location /kratos/ {
                 proxy_pass http://kratos/;
                 proxy_http_version 1.1;
                 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-Port $server_port;
                 proxy_set_header x-forwarded-proto $scheme;
                 proxy_set_header Upgrade $http_upgrade;
                 proxy_set_header Connection 'upgrade';
                 proxy_cache_bypass $http_upgrade;
        }

       location /kratos-admin/ {
                 proxy_pass http://kratos-admin/;
                 proxy_http_version 1.1;
                 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-Port $server_port;
                 proxy_set_header x-forwarded-proto $scheme;
                 proxy_set_header Upgrade $http_upgrade;
                 proxy_set_header Connection 'upgrade';
                 proxy_cache_bypass $http_upgrade;
         }

    listen [::]:443 ssl ipv6only=on; # managed by Certbot
    listen 443 ssl; # managed by Certbot
    certs go here...
}

我不明白为什么它在生产服务器上不起作用,这一定与ssl有关。

这是我正在使用的http客户端(ky.js,但它与fetch相同):

const options = {
  prefixUrl: 'https://example.com/api/v1',
  headers: {
    'Content-Type': 'application/json; charset=UTF-8',
    Accept: 'application/json',
  },
  timeout: 5000,
  mode: 'cors',
  credentials: 'include',
};

export const apiClient = ky.create(options);

我只是调用了我的受保护的后端路由,该路由受到AuthRequired中间件的保护,没有什么特别的:

function createTodo(data) {
  return apiClient.post(`todos`, { json: data }).json();
}

ory/kratos-client(js sdk)配置如下:

const conf = new Configuration({
  basePath: 'https://example.com/kratos',
  // 这些是axios选项(kratos js sdk在内部使用axios)
  baseOptions: { 
    withCredentials: true,
    timeout: 5000,
  },
});

export const kratos = new PublicApi(conf);

奇怪的是,在Firefox中,我在开发工具面板中看到了2个cookie,但在Chrome中没有。

这是csrf的一个:

aHR0cHM6Ly9hbmltb25kLnh5ei9rcmF0b3Mv_csrf_token:"Kx+PXWeoxsDNxQFGZBgvlTJScg9VIYEB+6cTrC0zsA0="
Created:"Thu, 27 May 2021 10:21:45 GMT"
Domain:".example.com"
Expires / Max-Age:"Fri, 27 May 2022 10:22:32 GMT"
HostOnly:false
HttpOnly:true
Last Accessed:"Thu, 27 May 2021 10:22:32 GMT"
Path:"/kratos/"
SameSite:"None"
Secure:true
Size: 91

这是会话cookie:

ory_kratos_session:"MTYyMjExMDk1MnxEdi1CQkFFQ180SUFBUkFCRUFBQVJfLUNBQUVHYzNSeWFXNW5EQThBRFhObGMzTnBiMjVmZEc5clpXNEdjM1J5YVc1bkRDSUFJRFZYV25Jd05HaEpTR28xVHpaT1kzTXlSSGxxVHpaaWQyUTVRamhIY2paM3zb24EtkN6Bmv_lRZa7YSRBOYvUGYSUBmZ7RIkDsm4Oyw=="
Created:"Thu, 27 May 2021 10:22:32 GMT"
Domain:".example.com"
Expires / Max-Age:"Thu, 08 Jul 2021 01:22:32 GMT"
HostOnly:false
HttpOnly:true
Last Accessed:"Thu, 27 May 2021 10:22:32 GMT"
Path:"/"
SameSite:"Lax"
Secure:true
Size:234

我以为这与容器中的时区有关,我也在所有容器中挂载了这个卷:-v /etc/localtime:/etc/localtime:ro

PS

问题是,每当我docker-compose restart kratos,事情就会出错,显然旧的csrf_token被使用。
这应该如何使用,我不能告诉我的用户,嘿,去你的浏览器删除所有缓存和cookie。
当我清除所有东西时,它可以工作,但一旦我重启了nginx,它就不再工作了(docker-compose restart后也是如此)...非常奇怪。

这个人在这里遇到了同样的问题:csrf问题重启后

英文:

I am using Go with ory kratos in docker and everything works fine on my machine on localhost.
Auth works, all cookies are send and set and i can call my backend from SPA and be authenticated.

The problem is that on live server behind nginx and ssl, apparently one cookie is not being sent from my js client (only ory_kratos_session is being sent and not xxx_csrf_token cookie) and it fails in function bellow with cookie missing error.

it uses official go sdk: kratos-client-go

Go AuthRequired middleware

func ExtractKratosCookiesFromRequest(r *http.Request) (csrf, session *http.Cookie, cookieHeader string) {
	cookieHeader = r.Header.Get("Cookie")

	cookies := r.Cookies()
	for _, c := range cookies {
		if c != nil {
			if ok := strings.HasSuffix(c.Name, string("csrf_token")); ok {
				csrf = c
			}
		}
	}

	sessionCookie, _ := r.Cookie("ory_kratos_session")
	if sessionCookie != nil {
		session = sessionCookie
	}

	return
}

func AuthRequired(w http.ResponseWriter, r *http.Request) error {
	csrfCookie, sessionCookie, cookieHeader := ExtractKratosCookiesFromRequest(r)
	if (csrfCookie == nil || sessionCookie == nil) || (csrfCookie.Value == "" || sessionCookie.Value == "") {
		return errors.New("Cookie missing")
	}

	req := kratos.PublicApi.Whoami(r.Context()).Cookie(cookieHeader)
	kratosSession, _, err := req.Execute()
	if err != nil {
		return errors.New("Whoami error")
	}
	
	return nil
}

My js http client has option: credentials: 'include'.

In devtools panel i see only 1 cookie (ory_kratos_session) after register/login.

So what is failing is that request is only sending ory_kratos_session and not xxx_csrf_token cookie (which works on localhost in kratos --dev mode, and cookie is vidisble in devtools panel)

Request Info

General:

Request URL: https://example.com/api/v1/users/1/donations
Request Method: GET
Status Code: 401 Unauthorized
Remote Address: 217.163.23.144:443
Referrer Policy: strict-origin-when-cross-origin

Request Headers:

accept: application/json
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
content-type: application/json; charset=UTF-8
Cookie: ory_kratos_session=MTYyMjA0NjEyMHxEdi1CQkFFQ180SUFBUkFCRUFBQVJfLUNBQUVHYzNSeWFXNW5EQThBRFhObGMzTnBiMjVmZEc5clpXNEdjM1J5YVc1bkRDSUFJRFo0Y2tKUFNFUmxZWFpsV21kaFdVbFZjMFU0VVZwcFkxbDNPRFpoY1ZOeXyInl242jY9c2FDQmykJrjLTNLg-sPFv2y04Qfl3uDfpA==
Host: example.com
Referer: https://example.com/dashboard/donations
sec-ch-ua: " Not;A Brand";v="99", "Google Chrome";v="91", "Chromium";v="91"
sec-ch-ua-mobile: ?0
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36

Response Headers:

Connection: keep-alive
Content-Length: 175
Content-Type: application/json
Date: Wed, 26 May 2021 17:12:27 GMT
Server: nginx/1.18.0 (Ubuntu)
Vary: Origin

docker-compose.yml

version: "3.8"

services:
  # --------------------------------------------------------------------------------
  api-server:
    build:
      context: .
      dockerfile: ./dockerfiles/app.dockerfile
    container_name: api-server
    restart: always
    volumes:
      - ./:/app
    ports:
      - 3001:3001
    networks:
      - intranet
    depends_on:
      - postgresd
  # --------------------------------------------------------------------------------
  postgresd:
    image: postgres:13.3-alpine
    container_name: postgresd
    restart: always
    environment:
      - POSTGRES_DB=test
      - POSTGRES_USER=test
      - POSTGRES_PASSWORD=test
    volumes:
      - postgres-data:/var/lib/postgresql/data
    ports:
      - 5432:5432
    networks:
      - intranet
  # --------------------------------------------------------------------------------
  kratos-migrate:
    image: oryd/kratos:v0.6.2-alpha.1
    container_name: kratos-migrate
    restart: on-failure
    environment:
      - DSN=postgres://test:test@postgresd:5432/test?sslmode=disable&max_conns=20&max_idle_conns=4
    volumes:
      - type: bind
        source: ./kratos/config
        target: /etc/config/kratos
    command:
      [
        "migrate",
        "sql",
        "--read-from-env",
        "--config",
        "/etc/config/kratos/kratos.yml",
        "--yes",
      ]
    networks:
      - intranet
    depends_on:
      - postgresd
  # --------------------------------------------------------------------------------
  kratos:
    image: oryd/kratos:v0.6.2-alpha.1
    container_name: kratos
    restart: unless-stopped
    environment:
      - DSN=postgres://test:test@postgresd:5432/test?sslmode=disable&max_conns=20&max_idle_conns=4
    command: ["serve", "--config", "/etc/config/kratos/kratos.yml"]
    volumes:
      - type: bind
        source: ./kratos/config
        target: /etc/config/kratos
    ports:
      - 4433:4433
      - 4434:4434
    networks:
      - intranet
    depends_on:
      - postgress
      - kratos-migrate
  # --------------------------------------------------------------------------------

volumes:
  postgres-data:

networks:
  intranet:
    driver: bridge

kratos.yml

version: v0.6.2-alpha.1

dsn: postgres://test:test@postgresd:5432/test?sslmode=disable&max_conns=20&max_idle_conns=4

serve:
  public:
    base_url: https://example.com/kratos/
    cors:
      enabled: true
      debug: true
      allow_credentials: true
      options_passthrough: true
      allowed_origins:
        - https://example.com
      allowed_methods:
        - POST
        - GET
        - PUT
        - PATCH
        - DELETE
        - OPTIONS
      allowed_headers:
        - Authorization
        - Cookie
        - Origin
        - X-Session-Token
      exposed_headers:
        - Content-Type
        - Set-Cookie
  admin:
    base_url: https://example.com/kratos/

selfservice:
  default_browser_return_url: https://example.com
  whitelisted_return_urls:
    - https://example.com
    - https://example.com/dashboard
    - https://example.com/auth/login
  methods:
    password:
      enabled: true
    oidc:
      enabled: false
    link:
      enabled: true
    profile:
      enabled: true
  flows:
    error:
      ui_url: https://example.com/error
    settings:
      ui_url: https://example.com/dashboard/profile
      privileged_session_max_age: 15m
    recovery:
      enabled: true
      ui_url: https://example.com/auth/recovery
      after:
        default_browser_return_url: https://example.com/auth/login
    verification:
      enabled: true
      ui_url: https://example.com/auth/verification
      after:
        default_browser_return_url: https://example.com
    logout:
      after:
        default_browser_return_url: https://example.com
    login:
      ui_url: https://example.com/auth/login
      lifespan: 10m
    registration:
      lifespan: 10m
      ui_url: https://example.com/auth/registration
      after:
        password:
          hooks:
            - hook: session
          default_browser_return_url: https://example.com/auth/login
        default_browser_return_url: https://example.com/auth/login
        oidc:
          hooks:
            - hook: session

secrets:
  cookie:
    - fdwfhgwjfgwf9286f24tf29ft

session:
  lifespan: 24h
  cookie:
    domain: example.com # i tried also with http:// and https://
    same_site: Lax

hashers:
  argon2:
    parallelism: 1
    memory: 128MB
    iterations: 1
    salt_length: 16
    key_length: 16

identity:
  default_schema_url: file:///etc/config/kratos/identity.schema.json

courier:
  smtp:
    connection_uri: smtp://user:pwd@smtp.mailtrap.io:2525
    from_name: test
    from_address: office@test.com

watch-courier: true

log:
  level: debug
  format: text
  leak_sensitive_values: true

My Go rest api has these cors options:

ALLOWED_ORIGINS=https://example.com
ALLOWED_METHODS=GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS
ALLOWED_HEADERS=Content-Type,Authorization,Cookie,Origin,X-Session-Token,X-CSRF-Token,Vary
EXPOSED_HEADERS=Content-Type,Authorization,Content-Length,Cache-Control,Content-Language,Content-Range,Set-Cookie,Pragma,Expires,Last-Modified,X-Session-Token,X-CSRF-Token
MAX_AGE=86400
ALLOW_CREDENTIALS=true

nginx default

upstream go-api {
    server 127.0.0.1:3001;
}

upstream kratos {
    server 127.0.0.1:4433;
}

upstream kratos-admin {
    server 127.0.0.1:4434;
}

server {
        server_name example.com www.example.com;

        location / {
                root /var/www/website;
                try_files $uri $uri/ /index.html;
        }
  
        location /api/ {
                 proxy_pass http://go-api;
                 proxy_http_version 1.1;
                 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-Port $server_port;
                 proxy_set_header x-forwarded-proto $scheme;
                 proxy_set_header Upgrade $http_upgrade;
                 proxy_set_header Connection 'upgrade';
                 proxy_cache_bypass $http_upgrade;
        }

        location /kratos/ {
                 proxy_pass http://kratos/;
                 proxy_http_version 1.1;
                 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-Port $server_port;
                 proxy_set_header x-forwarded-proto $scheme;
                 proxy_set_header Upgrade $http_upgrade;
                 proxy_set_header Connection 'upgrade';
                 proxy_cache_bypass $http_upgrade;
        }

       location /kratos-admin/ {
                 proxy_pass http://kratos-admin/;
                 proxy_http_version 1.1;
                 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-Port $server_port;
                 proxy_set_header x-forwarded-proto $scheme;
                 proxy_set_header Upgrade $http_upgrade;
                 proxy_set_header Connection 'upgrade';
                 proxy_cache_bypass $http_upgrade;
         }

    listen [::]:443 ssl ipv6only=on; # managed by Certbot
    listen 443 ssl; # managed by Certbot
    certs go here...
}

I don't understand why it is not working on live server, it has to be something with ssl

this is my http client that i am using (ky.js but it doesn't matter its the same as fetch)

const options = {
  prefixUrl: 'https://example.com/api/v1',
  headers: {
    'Content-Type': 'application/json; charset=UTF-8',
    Accept: 'application/json',
  },
  timeout: 5000,
  mode: 'cors',
  credentials: 'include',
};

export const apiClient = ky.create(options);

i'm just calling my backend protected route that is protected with AuthRequired middleware, nothing special:

function createTodo(data) {
  return apiClient.post(`todos`, { json: data }).json();
}

ory/kratos-client (js sdk) is configured like this:

const conf = new Configuration({
  basePath: 'https://example.com/kratos',
  // these are axios options (kratos js sdk uses axios under the hood)
  baseOptions: { 
    withCredentials: true,
    timeout: 5000,
  },
});

export const kratos = new PublicApi(conf);

It's strange that in firefox i see 2 cookies in devtools panel but not in chrome.

this is csrf one:

aHR0cHM6Ly9hbmltb25kLnh5ei9rcmF0b3Mv_csrf_token:"Kx+PXWeoxsDNxQFGZBgvlTJScg9VIYEB+6cTrC0zsA0="
Created:"Thu, 27 May 2021 10:21:45 GMT"
Domain:".example.com"
Expires / Max-Age:"Fri, 27 May 2022 10:22:32 GMT"
HostOnly:false
HttpOnly:true
Last Accessed:"Thu, 27 May 2021 10:22:32 GMT"
Path:"/kratos/"
SameSite:"None"
Secure:true
Size: 91

This is the session cookie:

ory_kratos_session:"MTYyMjExMDk1MnxEdi1CQkFFQ180SUFBUkFCRUFBQVJfLUNBQUVHYzNSeWFXNW5EQThBRFhObGMzTnBiMjVmZEc5clpXNEdjM1J5YVc1bkRDSUFJRFZYV25Jd05HaEpTR28xVHpaT1kzTXlSSGxxVHpaaWQyUTVRamhIY2paM3zb24EtkN6Bmv_lRZa7YSRBOYvUGYSUBmZ7RIkDsm4Oyw=="
Created:"Thu, 27 May 2021 10:22:32 GMT"
Domain:".example.com"
Expires / Max-Age:"Thu, 08 Jul 2021 01:22:32 GMT"
HostOnly:false
HttpOnly:true
Last Accessed:"Thu, 27 May 2021 10:22:32 GMT"
Path:"/"
SameSite:"Lax"
Secure:true
Size:234

I thought it was something related to the timezones in the containers, i also mounted this volume in all of them: -v /etc/localtime:/etc/localtime:ro

PS

The problem is that whenever i docker-compose restart kratos, things get broken, somehow apparently old csrf_token is being used.
How is this supposed to be used, i can't just tell my users hey go to your browser and delete all cache and cookies.
when i prune everything it works, but once i restarted nginx and it didn't work after that (same is after docker-compose restart)... very strange

this guy had the same problem here: csrf problem after restart

答案1

得分: 1

我相信你的kratos配置是不正确的。
属性serve.public.base_url应该是请求来源的URL,例如https://example.com/kratos/,而不是你的本地主机http://127.0.0.1:4433

另外,我建议你的管理端点不应该对外公开,你的后端服务应该在内部网络上请求管理URL(例如在Docker内部或本地主机上)。你的serve.admin.base_url应该是http://127.0.0.1:4434,并从nginx中移除。

根据我看来,nginx配置是正确的。我相信你只需要以下内容使其正常工作:

location /kratos/ {
    proxy_set_header Host $host;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_pass http://127.0.0.1:4433;
}
英文:

I believe your kratos configurations are incorrect.
The property serve.public.base_url should be the url the request is originating from e.g. https://example.com/kratos/ instead of your localhost http://127.0.0.1:4433.

Also just a word of recommendation, your admin endpoint should never be exposed to the public, your backend services should request the admin url on an internal network (e.g. inside docker or on localhost). Your serve.admin.base_url should be http://127.0.0.1:4434 instead and removed from nginx.

The nginx configurations seem correct to me. I believe you only require this for it to work:

location /kratos/ {
    proxy_set_header Host $host;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_pass http://127.0.0.1:4433;
}

答案2

得分: 0

我确实成功解决了这个问题,在生产环境中不使用--dev标志也能正常工作,并且即使在重新启动服务和更改所有内容之后也不会出现问题。

也许是我在React表单中使用了defaultValue来设置csrf令牌输入框的值。

现在,无论window.location的URL变化还是csrf_token的变化,它都应该使用最新的csrf_token值。

const [csrf, setCsrf] = React.useState('');

useEffect(() => {
  if (flowResponse !== null) {
    const csrfVal = flowResponse?.ui?.nodes?.find?.(n => n.attributes.name === 'csrf_token')?.attributes.value;
    setCsrf(csrfVal);
  }
}, [flowResponse, csrf]);

<input type='hidden' name='csrf_token' value={csrf} readOnly required />

最糟糕的是,可能是尾部斜杠或其他一些微小的问题,我不确定具体是什么导致了这个问题。

这里是所有对我有效的配置:

之前我尝试过将kratos的URL设置为http://127.0.0.1:4433或http://kratos:4433,但它不起作用(尽管我在这三者之间切换哈哈)。

初始化kratos客户端

conf := kratos.NewConfiguration()
conf.Servers[0].URL = "https://example.com/kratos/"
kratosClient := kratos.NewAPIClient(conf)

kratos.yml

version: v0.6.2-alpha.1

dsn: postgres://test:test@postgresd:5432/test?sslmode=disable&amp;max_conns=20&amp;max_idle_conns=4

serve:
  public:
    base_url: https://example.com/kratos/
    cors:
      enabled: true
      debug: true
      allow_credentials: true
      options_passthrough: true
      max_age: 0
      allowed_origins:
        - https://example.com
      allowed_methods:
        - POST
        - GET
        - PUT
        - PATCH
        - DELETE
        - OPTIONS
      allowed_headers:
        - Authorization
        - Cookie
        - Origin
        - X-Session-Token
      exposed_headers:
        - Content-Type
        - Set-Cookie
  admin:
    base_url: http://127.0.0.1:4434/

selfservice:
  default_browser_return_url: https://example.com
  whitelisted_return_urls:
    - https://example.com
    - https://example.com/dashboard
    - https://example.com/auth/login
  methods:
    password:
      enabled: true
    oidc:
      enabled: false
    link:
      enabled: true
    profile:
      enabled: true
  flows:
    error:
      ui_url: https://example.com/error
    settings:
      ui_url: https://example.com/dashboard/profile
      privileged_session_max_age: 15m
    recovery:
      enabled: true
      ui_url: https://example.com/auth/recovery
      after:
        default_browser_return_url: https://example.com/auth/login
    verification:
      enabled: true
      ui_url: https://example.com/auth/verification
      after:
        default_browser_return_url: https://example.com
    logout:
      after:
        default_browser_return_url: https://example.com
    login:
      ui_url: https://example.com/auth/login
      lifespan: 10m
    registration:
      lifespan: 10m
      ui_url: https://example.com/auth/registration
      after:
        password:
          hooks:
            - hook: session
          default_browser_return_url: https://example.com/auth/login
        default_browser_return_url: https://example.com/auth/login
        oidc:
          hooks:
            - hook: session

secrets:
  cookie:
    - veRy_S3cRet_tHinG

session:
  lifespan: 24h
  cookie:
    domain: example.com
    same_site: Lax
    path: /           
// <- 我之前没有设置path,不确定是否会有什么变化,但它可以工作(在之前,csrf cookie的路径为/kratos,现在它的路径为/,与session_cookie相同)

hashers:
  argon2:
    parallelism: 1
    memory: 128MB
    iterations: 1
    salt_length: 16
    key_length: 16

identity:
  default_schema_url: file:///etc/config/kratos/identity.schema.json

courier:
  smtp:
    connection_uri: smtp://user:pwd@smtp.mailtrap.io:2525
    from_name: example
    from_address: office@example.cpm

watch-courier: true

log:
  level: debug
  format: text
  leak_sensitive_values: true

nginx.conf

server {
    server_name example.com www.example.com;

    location / {
       root /var/www/public;
       try_files $uri $uri/ /index.html;
    }

    location /api/ {
      proxy_pass http://127.0.0.1:3001; // 后端API URL
      proxy_http_version 1.1;
      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-Port $server_port;
      proxy_set_header x-forwarded-proto $scheme;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection 'upgrade';
      proxy_cache_bypass $http_upgrade;
    }

    location /kratos/ {
      proxy_pass http://127.0.0.1:4433/;  // kratos公共URL
      proxy_http_version 1.1;
      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-Port $server_port;
      proxy_set_header x-forwarded-proto $scheme;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection 'upgrade';
      proxy_cache_bypass $http_upgrade;
    }
       
    listen [::]:443 ssl ipv6only=on; # 由Certbot管理
    listen 443 ssl; # 由Certbot管理
    证书...
}

server {
    if ($host = www.example.com) {
        return 301 https://$host$request_uri;
    } # 由Certbot管理

    if ($host = example.com) {
        return 301 https://$host$request_uri;
    } # 由Certbot管理

   listen 80 default_server;
   listen [::]:80 default_server;
   server_name example.com www.example.com;
   return 404; # 由Certbot管理
}
英文:

I did manage to solve this, it works fine without --dev flag in production and doesn't get screwed up even after restarting the services and changing everything.

Maybe it was even my react form where i used defaultValue for the csrf token input

Now whenever window.location url changes or whenever csrf_token changes, it should use the latest value for the csrf_token

const [csrf, setCsrf] = React.useState(&#39;&#39;);

useEffect(() =&gt; {
  if (flowResponse !== null) {
    const csrfVal = flowResponse?.ui?.nodes?.find?.(n =&gt; n.attributes.name === &#39;csrf_token&#39;)?.attributes.value;
    setCsrf(csrfVal);
  }
}, [flowResponse, csrf]);

&lt;input type=&#39;hidden&#39; name=&#39;csrf_token&#39; value={csrf} readOnly required /&gt;

the worst thing is that it could have also been a trailing slash or something so small that i am not sure exactly what caused it.

Here all post all the configuration that worked for me:

could have been that i tried before with this kratos url being http://127.0.0.1:4433 or http://kratos:4433 and it didn't work (even though i was switching between these 3 haha)

init kratos client

conf := kratos.NewConfiguration()
conf.Servers[0].URL = &quot;https://example.com/kratos/&quot;
kratosClient := kratos.NewAPIClient(conf)

kratos.yml

version: v0.6.2-alpha.1

dsn: postgres://test:test@postgresd:5432/test?sslmode=disable&amp;max_conns=20&amp;max_idle_conns=4

serve:
  public:
    base_url: https://example.com/kratos/
    cors:
      enabled: true
      debug: true
      allow_credentials: true
      options_passthrough: true
      max_age: 0
      allowed_origins:
        - https://example.com
      allowed_methods:
        - POST
        - GET
        - PUT
        - PATCH
        - DELETE
        - OPTIONS
      allowed_headers:
        - Authorization
        - Cookie
        - Origin
        - X-Session-Token
      exposed_headers:
        - Content-Type
        - Set-Cookie
  admin:
    base_url: http://127.0.0.1:4434/

selfservice:
  default_browser_return_url: https://example.com
  whitelisted_return_urls:
    - https://example.com
    - https://example.com/dashboard
    - https://example.com/auth/login
  methods:
    password:
      enabled: true
    oidc:
      enabled: false
    link:
      enabled: true
    profile:
      enabled: true
  flows:
    error:
      ui_url: https://example.com/error
    settings:
      ui_url: https://example.com/dashboard/profile
      privileged_session_max_age: 15m
    recovery:
      enabled: true
      ui_url: https://example.com/auth/recovery
      after:
        default_browser_return_url: https://example.com/auth/login
    verification:
      enabled: true
      ui_url: https://example.com/auth/verification
      after:
        default_browser_return_url: https://example.com
    logout:
      after:
        default_browser_return_url: https://example.com
    login:
      ui_url: https://example.com/auth/login
      lifespan: 10m
    registration:
      lifespan: 10m
      ui_url: https://example.com/auth/registration
      after:
        password:
          hooks:
            - hook: session
          default_browser_return_url: https://example.com/auth/login
        default_browser_return_url: https://example.com/auth/login
        oidc:
          hooks:
            - hook: session

secrets:
  cookie:
    - veRy_S3cRet_tHinG

session:
  lifespan: 24h
  cookie:
    domain: example.com
    same_site: Lax
    path: /           
// &lt;- i didn&#39;t have path before, not sure if it changes anything but it works (before csrf cookie had path /kratos and now when it works it has path /, same as session_cookie)

hashers:
  argon2:
    parallelism: 1
    memory: 128MB
    iterations: 1
    salt_length: 16
    key_length: 16

identity:
  default_schema_url: file:///etc/config/kratos/identity.schema.json

courier:
  smtp:
    connection_uri: smtp://user:pwd@smtp.mailtrap.io:2525
    from_name: example
    from_address: office@example.cpm

watch-courier: true

log:
  level: debug
  format: text
  leak_sensitive_values: true

nginx.conf

server {
    server_name example.com www.example.com;

    location / {
       root /var/www/public;
       try_files $uri $uri/ /index.html;
    }

    location /api/ {
      proxy_pass http://127.0.0.1:3001; // backend api url
      proxy_http_version 1.1;
      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-Port $server_port;
      proxy_set_header x-forwarded-proto $scheme;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection &#39;upgrade&#39;;
      proxy_cache_bypass $http_upgrade;
    }

    location /kratos/ {
      proxy_pass http://127.0.0.1:4433/;  // kratos public url
      proxy_http_version 1.1;
      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-Port $server_port;
      proxy_set_header x-forwarded-proto $scheme;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection &#39;upgrade&#39;;
      proxy_cache_bypass $http_upgrade;
    }
       
    listen [::]:443 ssl ipv6only=on; # managed by Certbot
    listen 443 ssl; # managed by Certbot
    certs...
}

server {
    if ($host = www.example.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot

    if ($host = example.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot

   listen 80 default_server;
   listen [::]:80 default_server;
   server_name example.com www.example.com;
   return 404; # managed by Certbot
}

huangapple
  • 本文由 发表于 2021年5月27日 01:54:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/67710286.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定