Timshel/vaultwarden

Invalid refresh token

Halyul opened this issue · 9 comments

Subject of the issue

The client gets logged out after the expiry of access token while refresh token and offline access are enabled. Seems that vaultwarden is unable to connect to the sso and renew the access token using the refresh token. For some unknown reason, only mobile clients get logged out while the browser extension has no such a behaviour. Additionally, the mobile clients will prompt the account has already added sometimes at the login page and it seems that the vault can be decrypted with only master password without reauthenticating through sso to get a valid access token (see the second section of logs, which happened right after the end of the first section of logs).

Deployment environment

  • vaultwarden version: oidcwarden/vaultwarden-oidc:latest-alpine
  • Install method: docker

  • Clients used: android, ios

  • Reverse proxy and version: n/a

  • MySQL/MariaDB or PostgreSQL version: postgres:16-alpine

  • Other relevant details:

  • zitadel version: ghcr.io/zitadel/zitadel:latest
  • zitadel database version: postgres:16-alpine
  • vaultwarden envs:
DATABASE_URL
ADMIN_TOKEN
PASSWORD_HINTS_ALLOWED=false
YUBICO_CLIENT_ID
YUBICO_SECRET_KEY
EMAIL_CHANGE_ALLOWED=false
EXPERIMENTAL_CLIENT_FEATURE_FLAGS=fido2-vault-credentials,autofill-v2,autofill-overlay
PUSH_ENABLED=true
PUSH_INSTALLATION_ID
PUSH_INSTALLATION_KEY
SSO_ENABLED=true
SSO_ONLY=true
SSO_AUTHORITY
SSO_AUDIENCE_TRUSTED
SSO_CLIENT_ID
SSO_CLIENT_SECRET
SSO_SCOPES="email profile offline_access"
SSO_FRONTEND='override'
LOG_LEVEL=debug
SSO_DEBUG_TOKENS=true

Steps to reproduce

n/a

Expected behaviour

n/a

Actual behaviour

n/a

Troubleshooting data

[2024-02-25 05:51:52.366][request][INFO] POST /identity/connect/token
[2024-02-25 05:51:52.370][request][INFO] POST /identity/connect/token
[2024-02-25 05:51:52.496][reqwest::connect][DEBUG] starting new connection: https://zitadel.example.com/
[2024-02-25 05:51:52.501][h2::client][DEBUG] binding client connection
[2024-02-25 05:51:52.502][h2::client][DEBUG] client connection bound
[2024-02-25 05:51:52.502][h2::codec::framed_write][DEBUG] send frame=Settings { flags: (0x0), enable_push: 0, initial_window_size: 2097152, max_frame_size: 16384 }
[2024-02-25 05:51:52.502][h2::proto::connection][DEBUG] Connection; peer=Client
[2024-02-25 05:51:52.502][h2::codec::framed_read][DEBUG] received frame=Settings { flags: (0x0), header_table_size: 4096, max_concurrent_streams: 250, initial_window_size: 1048576, max_frame_size: 1048576, max_header_list_size: 1048896 }
[2024-02-25 05:51:52.502][h2::codec::framed_write][DEBUG] send frame=Settings { flags: (0x1: ACK) }
[2024-02-25 05:51:52.502][h2::codec::framed_read][DEBUG] received frame=WindowUpdate { stream_id: StreamId(0), size_increment: 983041 }
[2024-02-25 05:51:52.502][h2::codec::framed_write][DEBUG] send frame=WindowUpdate { stream_id: StreamId(0), size_increment: 5177345 }
[2024-02-25 05:51:52.502][h2::codec::framed_write][DEBUG] send frame=Headers { stream_id: StreamId(1), flags: (0x5: END_HEADERS | END_STREAM) }
[2024-02-25 05:51:52.503][h2::codec::framed_read][DEBUG] received frame=Settings { flags: (0x1: ACK) }
[2024-02-25 05:51:52.503][h2::proto::settings][DEBUG] received settings ACK; applying Settings { flags: (0x0), enable_push: 0, initial_window_size: 2097152, max_frame_size: 16384 }
[2024-02-25 05:51:52.503][reqwest::connect][DEBUG] starting new connection: https://zitadel.example.com/
[2024-02-25 05:51:52.507][h2::client][DEBUG] binding client connection
[2024-02-25 05:51:52.507][h2::client][DEBUG] client connection bound
[2024-02-25 05:51:52.507][h2::codec::framed_write][DEBUG] send frame=Settings { flags: (0x0), enable_push: 0, initial_window_size: 2097152, max_frame_size: 16384 }
[2024-02-25 05:51:52.507][h2::proto::connection][DEBUG] Connection; peer=Client
[2024-02-25 05:51:52.508][h2::codec::framed_read][DEBUG] received frame=Settings { flags: (0x0), header_table_size: 4096, max_concurrent_streams: 250, initial_window_size: 1048576, max_frame_size: 1048576, max_header_list_size: 1048896 }
[2024-02-25 05:51:52.508][h2::codec::framed_write][DEBUG] send frame=Settings { flags: (0x1: ACK) }
[2024-02-25 05:51:52.508][h2::codec::framed_read][DEBUG] received frame=WindowUpdate { stream_id: StreamId(0), size_increment: 983041 }
[2024-02-25 05:51:52.508][h2::codec::framed_write][DEBUG] send frame=WindowUpdate { stream_id: StreamId(0), size_increment: 5177345 }
[2024-02-25 05:51:52.508][h2::codec::framed_write][DEBUG] send frame=Headers { stream_id: StreamId(1), flags: (0x5: END_HEADERS | END_STREAM) }
[2024-02-25 05:51:52.508][h2::codec::framed_read][DEBUG] received frame=Settings { flags: (0x1: ACK) }
[2024-02-25 05:51:52.508][h2::proto::settings][DEBUG] received settings ACK; applying Settings { flags: (0x0), enable_push: 0, initial_window_size: 2097152, max_frame_size: 16384 }
[2024-02-25 05:51:52.517][h2::codec::framed_read][DEBUG] received frame=Headers { stream_id: StreamId(1), flags: (0x4: END_HEADERS) }
[2024-02-25 05:51:52.517][h2::codec::framed_read][DEBUG] received frame=Data { stream_id: StreamId(1), flags: (0x1: END_STREAM) }
[2024-02-25 05:51:52.518][h2::codec::framed_write][DEBUG] send frame=GoAway { error_code: NO_ERROR, last_stream_id: StreamId(0) }
[2024-02-25 05:51:52.518][h2::proto::connection][DEBUG] Connection::poll; connection error error=GoAway(b"", NO_ERROR, Library)
[2024-02-25 05:51:52.520][h2::codec::framed_read][DEBUG] received frame=Headers { stream_id: StreamId(1), flags: (0x4: END_HEADERS) }
[2024-02-25 05:51:52.520][h2::codec::framed_read][DEBUG] received frame=Data { stream_id: StreamId(1), flags: (0x1: END_STREAM) }
[2024-02-25 05:51:52.520][h2::codec::framed_write][DEBUG] send frame=GoAway { error_code: NO_ERROR, last_stream_id: StreamId(0) }
[2024-02-25 05:51:52.520][h2::proto::connection][DEBUG] Connection::poll; connection error error=GoAway(b"", NO_ERROR, Library)
[2024-02-25 05:51:52.578][reqwest::connect][DEBUG] starting new connection: https://zitadel.example.com/
[2024-02-25 05:51:52.579][reqwest::connect][DEBUG] starting new connection: https://zitadel.example.com/
[2024-02-25 05:51:52.581][h2::client][DEBUG] binding client connection
[2024-02-25 05:51:52.581][h2::client][DEBUG] client connection bound
[2024-02-25 05:51:52.581][h2::codec::framed_write][DEBUG] send frame=Settings { flags: (0x0), enable_push: 0, initial_window_size: 2097152, max_frame_size: 16384 }
[2024-02-25 05:51:52.581][h2::proto::connection][DEBUG] Connection; peer=Client
[2024-02-25 05:51:52.582][h2::codec::framed_read][DEBUG] received frame=Settings { flags: (0x0), header_table_size: 4096, max_concurrent_streams: 250, initial_window_size: 1048576, max_frame_size: 1048576, max_header_list_size: 1048896 }
[2024-02-25 05:51:52.582][h2::codec::framed_write][DEBUG] send frame=Settings { flags: (0x1: ACK) }
[2024-02-25 05:51:52.582][h2::codec::framed_read][DEBUG] received frame=WindowUpdate { stream_id: StreamId(0), size_increment: 983041 }
[2024-02-25 05:51:52.582][h2::codec::framed_write][DEBUG] send frame=WindowUpdate { stream_id: StreamId(0), size_increment: 5177345 }
[2024-02-25 05:51:52.582][h2::codec::framed_write][DEBUG] send frame=Headers { stream_id: StreamId(1), flags: (0x5: END_HEADERS | END_STREAM) }
[2024-02-25 05:51:52.582][h2::codec::framed_read][DEBUG] received frame=Settings { flags: (0x1: ACK) }
[2024-02-25 05:51:52.582][h2::proto::settings][DEBUG] received settings ACK; applying Settings { flags: (0x0), enable_push: 0, initial_window_size: 2097152, max_frame_size: 16384 }
[2024-02-25 05:51:52.583][h2::client][DEBUG] binding client connection
[2024-02-25 05:51:52.583][h2::client][DEBUG] client connection bound
[2024-02-25 05:51:52.583][h2::codec::framed_write][DEBUG] send frame=Settings { flags: (0x0), enable_push: 0, initial_window_size: 2097152, max_frame_size: 16384 }
[2024-02-25 05:51:52.583][h2::proto::connection][DEBUG] Connection; peer=Client
[2024-02-25 05:51:52.583][h2::codec::framed_read][DEBUG] received frame=Settings { flags: (0x0), header_table_size: 4096, max_concurrent_streams: 250, initial_window_size: 1048576, max_frame_size: 1048576, max_header_list_size: 1048896 }
[2024-02-25 05:51:52.584][h2::codec::framed_write][DEBUG] send frame=Settings { flags: (0x1: ACK) }
[2024-02-25 05:51:52.584][h2::codec::framed_read][DEBUG] received frame=WindowUpdate { stream_id: StreamId(0), size_increment: 983041 }
[2024-02-25 05:51:52.584][h2::codec::framed_write][DEBUG] send frame=WindowUpdate { stream_id: StreamId(0), size_increment: 5177345 }
[2024-02-25 05:51:52.584][h2::codec::framed_write][DEBUG] send frame=Headers { stream_id: StreamId(1), flags: (0x5: END_HEADERS | END_STREAM) }
[2024-02-25 05:51:52.584][h2::codec::framed_read][DEBUG] received frame=Settings { flags: (0x1: ACK) }
[2024-02-25 05:51:52.584][h2::proto::settings][DEBUG] received settings ACK; applying Settings { flags: (0x0), enable_push: 0, initial_window_size: 2097152, max_frame_size: 16384 }
[2024-02-25 05:51:52.592][h2::codec::framed_read][DEBUG] received frame=Headers { stream_id: StreamId(1), flags: (0x4: END_HEADERS) }
[2024-02-25 05:51:52.592][h2::codec::framed_read][DEBUG] received frame=Data { stream_id: StreamId(1), flags: (0x1: END_STREAM) }
[2024-02-25 05:51:52.592][h2::codec::framed_write][DEBUG] send frame=GoAway { error_code: NO_ERROR, last_stream_id: StreamId(0) }
[2024-02-25 05:51:52.592][h2::proto::connection][DEBUG] Connection::poll; connection error error=GoAway(b"", NO_ERROR, Library)
[2024-02-25 05:51:52.594][h2::codec::framed_read][DEBUG] received frame=Headers { stream_id: StreamId(1), flags: (0x4: END_HEADERS) }
[2024-02-25 05:51:52.594][h2::codec::framed_read][DEBUG] received frame=Data { stream_id: StreamId(1), flags: (0x1: END_STREAM) }
[2024-02-25 05:51:52.594][h2::codec::framed_write][DEBUG] send frame=GoAway { error_code: NO_ERROR, last_stream_id: StreamId(0) }
[2024-02-25 05:51:52.594][h2::proto::connection][DEBUG] Connection::poll; connection error error=GoAway(b"", NO_ERROR, Library)
[2024-02-25 05:51:52.654][reqwest::connect][DEBUG] starting new connection: https://zitadel.example.com/
[2024-02-25 05:51:52.656][reqwest::connect][DEBUG] starting new connection: https://zitadel.example.com/
[2024-02-25 05:51:52.657][h2::client][DEBUG] binding client connection
[2024-02-25 05:51:52.657][h2::client][DEBUG] client connection bound
[2024-02-25 05:51:52.657][h2::codec::framed_write][DEBUG] send frame=Settings { flags: (0x0), enable_push: 0, initial_window_size: 2097152, max_frame_size: 16384 }
[2024-02-25 05:51:52.657][h2::proto::connection][DEBUG] Connection; peer=Client
[2024-02-25 05:51:52.658][h2::codec::framed_read][DEBUG] received frame=Settings { flags: (0x0), header_table_size: 4096, max_concurrent_streams: 250, initial_window_size: 1048576, max_frame_size: 1048576, max_header_list_size: 1048896 }
[2024-02-25 05:51:52.658][h2::codec::framed_write][DEBUG] send frame=Settings { flags: (0x1: ACK) }
[2024-02-25 05:51:52.658][h2::codec::framed_read][DEBUG] received frame=WindowUpdate { stream_id: StreamId(0), size_increment: 983041 }
[2024-02-25 05:51:52.658][h2::codec::framed_write][DEBUG] send frame=WindowUpdate { stream_id: StreamId(0), size_increment: 5177345 }
[2024-02-25 05:51:52.658][h2::codec::framed_write][DEBUG] send frame=Headers { stream_id: StreamId(1), flags: (0x4: END_HEADERS) }
[2024-02-25 05:51:52.658][h2::codec::framed_write][DEBUG] send frame=Data { stream_id: StreamId(1), flags: (0x1: END_STREAM) }
[2024-02-25 05:51:52.658][h2::codec::framed_read][DEBUG] received frame=Settings { flags: (0x1: ACK) }
[2024-02-25 05:51:52.658][h2::proto::settings][DEBUG] received settings ACK; applying Settings { flags: (0x0), enable_push: 0, initial_window_size: 2097152, max_frame_size: 16384 }
[2024-02-25 05:51:52.660][h2::client][DEBUG] binding client connection
[2024-02-25 05:51:52.660][h2::client][DEBUG] client connection bound
[2024-02-25 05:51:52.660][h2::codec::framed_write][DEBUG] send frame=Settings { flags: (0x0), enable_push: 0, initial_window_size: 2097152, max_frame_size: 16384 }
[2024-02-25 05:51:52.660][h2::proto::connection][DEBUG] Connection; peer=Client
[2024-02-25 05:51:52.660][h2::codec::framed_read][DEBUG] received frame=Settings { flags: (0x0), header_table_size: 4096, max_concurrent_streams: 250, initial_window_size: 1048576, max_frame_size: 1048576, max_header_list_size: 1048896 }
[2024-02-25 05:51:52.660][h2::codec::framed_write][DEBUG] send frame=Settings { flags: (0x1: ACK) }
[2024-02-25 05:51:52.660][h2::codec::framed_read][DEBUG] received frame=WindowUpdate { stream_id: StreamId(0), size_increment: 983041 }
[2024-02-25 05:51:52.660][h2::codec::framed_write][DEBUG] send frame=WindowUpdate { stream_id: StreamId(0), size_increment: 5177345 }
[2024-02-25 05:51:52.660][h2::codec::framed_write][DEBUG] send frame=Headers { stream_id: StreamId(1), flags: (0x4: END_HEADERS) }
[2024-02-25 05:51:52.660][h2::codec::framed_write][DEBUG] send frame=Data { stream_id: StreamId(1), flags: (0x1: END_STREAM) }
[2024-02-25 05:51:52.660][h2::codec::framed_read][DEBUG] received frame=Settings { flags: (0x1: ACK) }
[2024-02-25 05:51:52.660][h2::proto::settings][DEBUG] received settings ACK; applying Settings { flags: (0x0), enable_push: 0, initial_window_size: 2097152, max_frame_size: 16384 }
[2024-02-25 05:51:52.811][request][INFO] POST /identity/connect/token
[2024-02-25 05:51:52.815][vaultwarden::auth][ERROR] Invalid refresh token
[2024-02-25 05:51:52.815][vaultwarden::api::identity][ERROR] {"ErrorModel":{"Message":"Invalid refresh token","Object":"error"},"ExceptionMessage":null,"ExceptionStackTrace":null,"InnerExceptionMessage":null,"Message":"Invalid refresh token","Object":"error","ValidationErrors":{"":["Invalid refresh token"]},"error":"","error_description":""}
[2024-02-25 05:51:52.815][response][INFO] (login) POST /identity/connect/token => 401 Unauthorized
[2024-02-25 05:51:53.156][request][INFO] POST /identity/connect/token
[2024-02-25 05:51:53.156][vaultwarden::auth][ERROR] Invalid refresh token
[2024-02-25 05:51:53.157][vaultwarden::api::identity][ERROR] {"ErrorModel":{"Message":"Invalid refresh token","Object":"error"},"ExceptionMessage":null,"ExceptionStackTrace":null,"InnerExceptionMessage":null,"Message":"Invalid refresh token","Object":"error","ValidationErrors":{"":["Invalid refresh token"]},"error":"","error_description":""}
[2024-02-25 05:51:53.157][response][INFO] (login) POST /identity/connect/token => 401 Unauthorized
[2024-02-25 05:51:53.271][request][INFO] GET /api/config/
[2024-02-25 05:51:53.271][response][INFO] (config) GET /api/config => 200 OK
[2024-02-25 05:51:53.391][request][INFO] GET /api/config/
[2024-02-25 05:51:53.391][response][INFO] (config) GET /api/config => 200 OK
[2024-02-25 05:51:53.504][request][INFO] GET /api/config/
[2024-02-25 05:51:53.504][response][INFO] (config) GET /api/config => 200 OK
[2024-02-25 05:51:53.545][request][INFO] GET /api/config/
[2024-02-25 05:51:53.545][response][INFO] (config) GET /api/config => 200 OK
[2024-02-25 05:51:53.622][h2::codec::framed_read][DEBUG] received frame=Headers { stream_id: StreamId(1), flags: (0x4: END_HEADERS) }
[2024-02-25 05:51:53.623][h2::codec::framed_read][DEBUG] received frame=Data { stream_id: StreamId(1), flags: (0x1: END_STREAM) }
[2024-02-25 05:51:53.623][h2::codec::framed_write][DEBUG] send frame=GoAway { error_code: NO_ERROR, last_stream_id: StreamId(0) }
[2024-02-25 05:51:53.623][h2::proto::connection][DEBUG] Connection::poll; connection error error=GoAway(b"", NO_ERROR, Library)
[2024-02-25 05:51:53.628][vaultwarden::sso][ERROR] Request to exchange_refresh_token endpoint failed: ServerResponse(StandardErrorResponse { error: server_error, error_description: Some("Errors.Internal"), error_uri: None })
[2024-02-25 05:51:53.628][vaultwarden::api::identity][ERROR] {"ErrorModel":{"Message":"Request to exchange_refresh_token endpoint failed: ServerResponse(StandardErrorResponse { error: server_error, error_description: Some(\"Errors.Internal\"), error_uri: None })","Object":"error"},"ExceptionMessage":null,"ExceptionStackTrace":null,"InnerExceptionMessage":null,"Message":"Request to exchange_refresh_token endpoint failed: ServerResponse(StandardErrorResponse { error: server_error, error_description: Some(\"Errors.Internal\"), error_uri: None })","Object":"error","ValidationErrors":{"":["Request to exchange_refresh_token endpoint failed: ServerResponse(StandardErrorResponse { error: server_error, error_description: Some(\"Errors.Internal\"), error_uri: None })"]},"error":"","error_description":""}
[2024-02-25 05:51:53.628][response][INFO] (login) POST /identity/connect/token => 401 Unauthorized
[2024-02-25 05:51:53.983][request][INFO] GET /api/config/
[2024-02-25 05:51:53.984][response][INFO] (config) GET /api/config => 200 OK
[2024-02-25 05:51:54.022][request][INFO] GET /api/config/
[2024-02-25 05:51:54.022][response][INFO] (config) GET /api/config => 200 OK
[2024-02-25 05:51:54.460][h2::codec::framed_read][DEBUG] received frame=Headers { stream_id: StreamId(1), flags: (0x4: END_HEADERS) }
[2024-02-25 05:51:54.460][h2::codec::framed_read][DEBUG] received frame=Data { stream_id: StreamId(1), flags: (0x1: END_STREAM) }
[2024-02-25 05:51:54.460][h2::codec::framed_write][DEBUG] send frame=GoAway { error_code: NO_ERROR, last_stream_id: StreamId(0) }
[2024-02-25 05:51:54.461][h2::proto::connection][DEBUG] Connection::poll; connection error error=GoAway(b"", NO_ERROR, Library)
[2024-02-25 05:51:54.461][vaultwarden::sso][DEBUG] Non jwt refresh_token (expiration set to 1709463114)
[2024-02-25 05:51:54.467][response][INFO] (login) POST /identity/connect/token => 200 OK
[2024-02-25 05:51:54.806][request][INFO] GET /api/config/
[2024-02-25 05:51:54.806][response][INFO] (config) GET /api/config => 200 OK
[2024-02-25 05:51:57.159][vaultwarden::api::core::accounts][DEBUG] Purging auth requests
[2024-02-25 05:51:57.159][vaultwarden::api::core::two_factor][DEBUG] Sending notifications for incomplete 2FA logins
[2024-02-25 05:51:58.442][request][INFO] GET /api/config/
[2024-02-25 05:51:58.442][response][INFO] (config) GET /api/config => 200 OK
[2024-02-25 05:51:58.444][request][INFO] PUT /api/devices/identifier/<uuid>/token
[2024-02-25 05:51:58.449][vaultwarden::api::core::accounts][DEBUG] Device <uuid> is already registered and token is the same
[2024-02-25 05:51:58.449][response][INFO] (put_device_token) PUT /api/devices/identifier/<uuid>/token => 200 OK
[2024-02-25 05:52:37.573][request][INFO] GET /alive
[2024-02-25 05:52:37.573][response][INFO] (alive) GET /alive => 200 OK
[2024-02-25 05:52:47.467][request][INFO] GET /api/sync
[2024-02-25 05:52:47.553][response][INFO] (sync) GET /api/sync?<data..> => 200 OK
[2024-02-25 05:52:48.541][request][INFO] GET /api/config/
[2024-02-25 05:52:48.541][response][INFO] (config) GET /api/config => 200 OK

Zitadel uses http/2 for communication only, if connection error error=GoAway(b"", NO_ERROR, Library) indicates a failure in communication between vaultwarden and zitadel, then this bug report shall move to dani-garcia/vaultwarden

Looking at the first log it appears there are 4 calls to the /identity/connect/token :

[2024-02-25 05:51:52.366][request][INFO] POST /identity/connect/token
[2024-02-25 05:51:52.370][request][INFO] POST /identity/connect/token
[2024-02-25 05:51:52.811][request][INFO] POST /identity/connect/token
[2024-02-25 05:51:53.156][request][INFO] POST /identity/connect/token

Both of the later calls will fail since they are done with expired refresh_token (a refresh_token cannot be reused so as soon as a call is done to the endpoint it won't work for another one).

For the others two calls : one appear to have worked :

[2024-02-25 05:51:54.460][h2::codec::framed_write][DEBUG] send frame=GoAway { error_code: NO_ERROR, last_stream_id: StreamId(0) }
[2024-02-25 05:51:54.461][h2::proto::connection][DEBUG] Connection::poll; connection error error=GoAway(b"", NO_ERROR, Library)
[2024-02-25 05:51:54.461][vaultwarden::sso][DEBUG] Non jwt refresh_token (expiration set to 1709463114)
[2024-02-25 05:51:54.467][response][INFO] (login) POST /identity/connect/token => 200 OK

So I'm not very fluent in HTTP/2, but then I would say connection error error=GoAway(b"", NO_ERROR, Library) is not a sign of failure.

If we look at the last response then :

[2024-02-25 05:51:53.623][h2::proto::connection][DEBUG] Connection::poll; connection error error=GoAway(b"", NO_ERROR, Library)
[2024-02-25 05:51:53.628][vaultwarden::sso][ERROR] Request to exchange_refresh_token endpoint failed: ServerResponse(StandardErrorResponse { error: server_error, error_description: Some("Errors.Internal"), error_uri: None })
[2024-02-25 05:51:53.628][vaultwarden::api::identity][ERROR] {"ErrorModel":{"Message":"Request to exchange_refresh_token endpoint failed: ServerResponse(StandardErrorResponse { error: server_error, error_description: Some(\"Errors.Internal\"), error_uri: None })","Object":"error"},"ExceptionMessage":null,"ExceptionStackTrace":null,"InnerExceptionMessage":null,"Message":"Request to exchange_refresh_token endpoint failed: ServerResponse(StandardErrorResponse { error: server_error, error_description: Some(\"Errors.Internal\"), error_uri: None })","Object":"error","ValidationErrors":{"":["Request to exchange_refresh_token endpoint failed: ServerResponse(StandardErrorResponse { error: server_error, error_description: Some(\"Errors.Internal\"), error_uri: None })"]},"error":"","error_description":""}
[2024-02-25 05:51:53.628][response][INFO] (login) POST /identity/connect/token => 401 Unauthorized

It's responding with an Errors.Internal so I'm guessing it triggers an error 500 on Zitadel side, did you check the logs to see if you found anything ?

I checked Zitadel's logs, all of them are 200 and the error is nowhere to be found. After checking Zitadel's admin console, It looks like a refresh token was issued normally, which is confusing.

Also, I have manually tested using curl, I was unable to reproduce Errors.Internal seen in vaultwarden's logs, which prevents me from further debugging unless I know how vaultwarden communicates with Zitadel internally regarding to this issue :(.

Hey

Just tested running Zitadel locally with the docker-compose they provide.

I can't reproduce your issue.
Can you describe the flow that fails on mobile ?

To test I set the Access Token Lifetime to 0.1 which mean that after 1 min BitWarden will consider it expired since expiration will be in less than 5min, did not change the other values.

Then I tested the following scenarios in the Android application version 2024.2.0 (9000):

  • Wait for expiration then call synchronize ⇒ ok
  • Lock screen then wait for expiration then unlock ⇒ ok
  • Lock screen / minimize app, wait for expiration then launch and unlock ⇒ ok
  • Lock screen / kill app, wait for expiration then launch and unlock ⇒ ok
  • Kill app, wait for expiration then launch and unlock ⇒ ok

Lowered the session expiration in the application to 1 min then :

  • Wait for expiration then call synchronize ⇒ ok
  • Minimize then wait for expiration then launch and unlock ⇒ ok
  • Kill app, wait for expiration then launch and unlock ⇒ ok

I have tried 0.1, getting logged out after 6 mins when I reopened the app, because 0.1 hour is 6 mins. Could you try again?

I pushed the modified docker-compose I used.

It uses a .env (copy the .env.template) file and use the host network for Zitadel to allow to bind an external ip (set ZITADEL_EXTERNALDOMAIN) to be able to test mobile applications. Can you test with it ?

I'm currently under a strictly restricted network, which prevents me from connecting to lan machines. I will test it out after one or two weeks and let you know.

In the other issue you mention that

Updated: Refresh token is working correctly in PKCE. The client no longer gets logged out like before.

No idea how that's possible since it should not change the response of the exhange_code ...
Looking back at the Vaultwarden version you are using : oidcwarden/vaultwarden-oidc:latest-alpine, I would not run with latest since it means you are not sure what you are running and when I publish a new version I might introduce breaking change (I try not to but since it's a test version I might).

I'm currently under a strictly restricted network, which prevents me from connecting to lan machines. I will test it out after one or two weeks and let you know.

If you can't change much I would try to explore why do you have so many calls to token endpoint (I never had more than one call when testing all the scenarios on Android) . Cf :

[2024-02-25 05:51:52.366][request][INFO] POST /identity/connect/token
[2024-02-25 05:51:52.370][request][INFO] POST /identity/connect/token
[2024-02-25 05:51:52.811][request][INFO] POST /identity/connect/token
[2024-02-25 05:51:53.156][request][INFO] POST /identity/connect/token

If it's from the same client then it's expected that you will get logout since one of those calls will be made without the correct Vaultwarden refresh_token (the VW refresh_token value is changed with each call and invalidate previous ones).

Not sure what really happened before. Those 4 requests were sent from the same client as I was testing using only one ios device. But now, I can only see one /identity/connect/token request (w/o PKCE) and the refresh token works as intended while the only change in the system is updating to the latest oidcwarden/vaultwarden-oidc. It is possible that those 4 requests caused the issue, but still unsure about the underlying reason.

Looking back at the Vaultwarden version you are using oidcwarden/vaultwarden-oidc:latest-alpine, I would not run with latest since it means you are not sure what you are running and when I publish a new version I might introduce breaking change

I'm completely agree with you, but ATM I will pull the latest image using oidcwarden/vaultwarden-oidc:latest-alpine before any testing and submitting issues. Thus, I believe I can safely assume that the latest published version is used.