[Bug]: The authorization code has already been used. #2485

Closed
opened 2026-02-28 20:15:30 -06:00 by GiteaMirror · 6 comments
Owner

Originally created by @Cheezzhead on GitHub (Sep 19, 2025).

Verified issue does not already exist?

  • I have searched and found no existing issue

What happened?

I've implemented OpenID with Authelia as OIDC provider. It all worked well, until a few minutes ago: I decided to try to log into the desktop client with my OpenID credentials, which failed with an openid-grant-failed error, and the below log entry in Authelia:

Access Request failed with error: The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client. The authorization code has already been used.

If this were only the desktop client failing, I wouldn't worry much (but it would be nice to have confirmation that OpenID authentication doesn't work with the desktop client). However, from that point on any attempted login from the web also fails with the same error. In other words, I currently am unable to login into my AB account at all.

docker-compose:

actual-budget:
    image: docker.io/actualbudget/actual-server:latest
    container_name: actual-budget
    extends:
      file: ${SHARED_DIR}/common.yml
      service: default
    #ports: [5006:5006/tcp]
    healthcheck:
      test: ['CMD-SHELL', 'node src/scripts/health-check.js']
    secrets: [actual_budget_oidc_client_secret]
    env_file: 
      - ./actual-budget/oidc_authelia.env
    #environment:
    #  Setting this through a secret file results in openid-grant-failed right now.
    #  ACTUAL_OPENID_CLIENT_SECRET_FILE: /run/secrets/actual_budget_oidc_client_secret
    volumes:
      - ${DOCKER_CONF_DIR}/actual-budget:/data

oidc_authelia.env:

ACTUAL_OPENID_DISCOVERY_URL=https://auth.domain.tld
ACTUAL_OPENID_CLIENT_ID=secure_id
ACTUAL_OPENID_CLIENT_SECRET=secure_secret
ACTUAL_OPENID_SERVER_HOSTNAME=https://budget.domain.tld
ACTUAL_OPENID_AUTH_METHOD=oauth2
ACTUAL_LOGIN_METHOD=openid
ACTUAL_ALLOWED_LOGIN_METHODS=openid
#ACTUAL_OPENID_ENFORCE=true

How can we reproduce the issue?

  • Implement OIDC succesfully
  • Attempt to login from the Desktop client
  • Observe authentication failure from web

Where are you hosting Actual?

Docker

What browsers are you seeing the problem on?

Firefox

Operating System

Linux

Originally created by @Cheezzhead on GitHub (Sep 19, 2025). ### Verified issue does not already exist? - [x] I have searched and found no existing issue ### What happened? I've implemented OpenID with Authelia as OIDC provider. It all worked well, until a few minutes ago: I decided to try to log into the desktop client with my OpenID credentials, which failed with an `openid-grant-failed` error, and the below log entry in Authelia: ```Access Request failed with error: The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client. The authorization code has already been used.``` If this were only the desktop client failing, I wouldn't worry much (but it would be nice to have confirmation that OpenID authentication doesn't work with the desktop client). However, from that point on any attempted login from the web also fails with the same error. In other words, I currently am unable to login into my AB account at all. docker-compose: ``` actual-budget: image: docker.io/actualbudget/actual-server:latest container_name: actual-budget extends: file: ${SHARED_DIR}/common.yml service: default #ports: [5006:5006/tcp] healthcheck: test: ['CMD-SHELL', 'node src/scripts/health-check.js'] secrets: [actual_budget_oidc_client_secret] env_file: - ./actual-budget/oidc_authelia.env #environment: # Setting this through a secret file results in openid-grant-failed right now. # ACTUAL_OPENID_CLIENT_SECRET_FILE: /run/secrets/actual_budget_oidc_client_secret volumes: - ${DOCKER_CONF_DIR}/actual-budget:/data ``` oidc_authelia.env: ``` ACTUAL_OPENID_DISCOVERY_URL=https://auth.domain.tld ACTUAL_OPENID_CLIENT_ID=secure_id ACTUAL_OPENID_CLIENT_SECRET=secure_secret ACTUAL_OPENID_SERVER_HOSTNAME=https://budget.domain.tld ACTUAL_OPENID_AUTH_METHOD=oauth2 ACTUAL_LOGIN_METHOD=openid ACTUAL_ALLOWED_LOGIN_METHODS=openid #ACTUAL_OPENID_ENFORCE=true ``` ### How can we reproduce the issue? - Implement OIDC succesfully - Attempt to login from the Desktop client - Observe authentication failure from web ### Where are you hosting Actual? Docker ### What browsers are you seeing the problem on? Firefox ### Operating System Linux
GiteaMirror added the bug label 2026-02-28 20:15:30 -06:00
Author
Owner

@youngcw commented on GitHub (Sep 19, 2025):

Please reach out on discord for tech support

@youngcw commented on GitHub (Sep 19, 2025): Please reach out on discord for tech support
Author
Owner

@Cheezzhead commented on GitHub (Sep 22, 2025):

...This is not tech support, this is a bug. The OIDC provider is correctly configured, AB is configured according to the documentation. After working for a few days, I'm now getting the same error just by logging into the web client. something is going wrong in the AB code

@Cheezzhead commented on GitHub (Sep 22, 2025): ...This is not tech support, this is a bug. The OIDC provider is correctly configured, AB is configured according to the documentation. After working for a few days, I'm now getting the same error just by logging into the web client. **something is going wrong in the AB code**
Author
Owner

@youngcw commented on GitHub (Sep 22, 2025):

@lelemm

@youngcw commented on GitHub (Sep 22, 2025): @lelemm
Author
Owner

@lelemm commented on GitHub (Sep 23, 2025):

The desktop client (electron) creates a http server when you start the openid flow. So, the return url is the localhost url. That's probably what is going on. On authelia try to add the redirect url to accept

this is the default return url:
http://localhost:3010 but if this port is in use, it will use another port that will be displayed on the dev tools console of electron. You can check the code here:
753a105b3d/packages/desktop-electron/index.ts (L92C1-L111C10)

@lelemm commented on GitHub (Sep 23, 2025): The desktop client (electron) creates a http server when you start the openid flow. So, the return url is the localhost url. That's probably what is going on. On authelia try to add the redirect url to accept this is the default return url: `http://localhost:3010` but if this port is in use, it will use another port that will be displayed on the dev tools console of electron. You can check the code here: https://github.com/actualbudget/actual/blob/753a105b3dcaec4fc8b725cb9540528645f3d9ca/packages/desktop-electron/index.ts#L92C1-L111C10
Author
Owner

@Cheezzhead commented on GitHub (Sep 23, 2025):

Good to know! I'll definitely try that out, but I should clarify at this point that the problem has progressed beyond the Desktop client - to the point where I think that was a bit of a red herring (as in, the desktop client just happened to be the thing I was trying out).

I am now getting the 'openid-grant-failed' error on every login attempt. From the (debug) logs from Authelia it seems that everything is set up correctly:

{"level":"debug","method":"GET","msg":"Authorization Request with id 'a24b85af-5c7c-41b0-8bd6-30a48efde7d8' on client with id '<secure_id>' is being processed","path":"/api/oidc/authorization","remote_ip":"<external_ip>","time":"2025-09-23T13:06:33+02:00"} 
{"level":"debug","method":"GET","msg":"Checking the authentication backend for an updated profile for user","path":"/api/oidc/authorization","remote_ip":"<external_ip>","time":"2025-09-23T13:06:33+02:00","username":"<username>"} 
{"level":"debug","method":"GET","msg":"Authorization Request with id 'a24b85af-5c7c-41b0-8bd6-30a48efde7d8' on client with id '<secure_id>' was successfully processed, proceeding to build Authorization Response","path":"/api/oidc/authorization","remote_ip":"<external_ip>","time":"2025-09-23T13:06:33+02:00"} 
{"level":"debug","msg":"Check authorization of subject username=<username> groups=### ip=<external_ip> and object https://budget.domain.tld/openid/callback?code=<authorization_code>\u0026iss=https%3A%2F%2Fauth.domain.tld\u0026scope=openid+email+profile\u0026state=<state> (method GET).","time":"2025-09-23T13:06:33+02:00"} 
{"level":"debug","method":"POST","msg":"Access Request with id 'a24b85af-5c7c-41b0-8bd6-30a48efde7d8' on client with id '<secure_id>' is being processed","path":"/api/oidc/token","remote_ip":"<server_ip>","time":"2025-09-23T13:06:34+02:00"} 
{"level":"debug","method":"POST","msg":"Access Request with id 'a24b85af-5c7c-41b0-8bd6-30a48efde7d8' on client with id '<secure_id>' has successfully been processed","path":"/api/oidc/token","remote_ip":"<server_ip>","time":"2025-09-23T13:06:34+02:00"} 
{"level":"debug","method":"GET","msg":"User Info Request with id 'd21b1ddf-319d-41db-83e6-bf142b9bfed9' is being processed","path":"/api/oidc/userinfo","remote_ip":"<server_ip>","time":"2025-09-23T13:06:34+02:00"} 
{"level":"debug","method":"GET","msg":"User Info Request with id 'd21b1ddf-319d-41db-83e6-bf142b9bfed9' on client with id '<secure_id>' is being returned unsigned as per the registered client configuration","path":"/api/oidc/userinfo","remote_ip":"<server_ip>","time":"2025-09-23T13:06:34+02:00"} 
{"level":"debug","method":"GET","msg":"User Info Request with id 'd21b1ddf-319d-41db-83e6-bf142b9bfed9' on client with id '<secure_id>' was successfully processed","path":"/api/oidc/userinfo","remote_ip":"<server_ip>","time":"2025-09-23T13:06:34+02:00"} 
{"level":"debug","msg":"Check authorization of subject username= groups= ip=<external_ip> and object https://budget.domain.tld/favicon.ico (method GET).","time":"2025-09-23T13:06:34+02:00"}

As far as I can see, the entire OIDC flow completed cleanly, however AB logs a 400 response afterwards:

2025-09-23T11:05:17.418Z info: POST 200 /account/login 
2025-09-23T11:06:34.295Z info: GET 400 /openid/callback?code=<authorization_code>&iss=https%3A%2F%2Fauth.domain.tld&scope=openid+email+profile&state=<state>

Obviously I don't know anything about AB's internal OIDC flow, but it seems to me that after login, it's making a second request to its own callback endpoint with the same code that Authelia already consumed.

Here's Authelia's OIDC configuration for the Actual Budget client, for posterity:

- client_id: 'secure_id'
        client_name: 'Actual Budget'
        client_secret: 'hashed_secure_secret 
        public: false
        authorization_policy: 'one_factor'
        require_pkce: false
        pkce_challenge_method: ''
        redirect_uris:
          - 'https://budget.domain.tld/openid/callback'
        scopes:
          - 'openid'
          - 'profile'
          - 'groups'
          - 'email'
        response_types:
          - 'code'
        grant_types:
          - 'authorization_code'
        access_token_signed_response_alg: 'none'
        userinfo_signed_response_alg: 'none'
        token_endpoint_auth_method: 'client_secret_basic'
@Cheezzhead commented on GitHub (Sep 23, 2025): Good to know! I'll definitely try that out, but I should clarify at this point that the problem has progressed beyond the Desktop client - to the point where I think that was a bit of a red herring (as in, the desktop client just happened to be the thing I was trying out). I am now getting the 'openid-grant-failed' error on every login attempt. From the (debug) logs from Authelia it seems that everything is set up correctly: ``` {"level":"debug","method":"GET","msg":"Authorization Request with id 'a24b85af-5c7c-41b0-8bd6-30a48efde7d8' on client with id '<secure_id>' is being processed","path":"/api/oidc/authorization","remote_ip":"<external_ip>","time":"2025-09-23T13:06:33+02:00"} {"level":"debug","method":"GET","msg":"Checking the authentication backend for an updated profile for user","path":"/api/oidc/authorization","remote_ip":"<external_ip>","time":"2025-09-23T13:06:33+02:00","username":"<username>"} {"level":"debug","method":"GET","msg":"Authorization Request with id 'a24b85af-5c7c-41b0-8bd6-30a48efde7d8' on client with id '<secure_id>' was successfully processed, proceeding to build Authorization Response","path":"/api/oidc/authorization","remote_ip":"<external_ip>","time":"2025-09-23T13:06:33+02:00"} {"level":"debug","msg":"Check authorization of subject username=<username> groups=### ip=<external_ip> and object https://budget.domain.tld/openid/callback?code=<authorization_code>\u0026iss=https%3A%2F%2Fauth.domain.tld\u0026scope=openid+email+profile\u0026state=<state> (method GET).","time":"2025-09-23T13:06:33+02:00"} {"level":"debug","method":"POST","msg":"Access Request with id 'a24b85af-5c7c-41b0-8bd6-30a48efde7d8' on client with id '<secure_id>' is being processed","path":"/api/oidc/token","remote_ip":"<server_ip>","time":"2025-09-23T13:06:34+02:00"} {"level":"debug","method":"POST","msg":"Access Request with id 'a24b85af-5c7c-41b0-8bd6-30a48efde7d8' on client with id '<secure_id>' has successfully been processed","path":"/api/oidc/token","remote_ip":"<server_ip>","time":"2025-09-23T13:06:34+02:00"} {"level":"debug","method":"GET","msg":"User Info Request with id 'd21b1ddf-319d-41db-83e6-bf142b9bfed9' is being processed","path":"/api/oidc/userinfo","remote_ip":"<server_ip>","time":"2025-09-23T13:06:34+02:00"} {"level":"debug","method":"GET","msg":"User Info Request with id 'd21b1ddf-319d-41db-83e6-bf142b9bfed9' on client with id '<secure_id>' is being returned unsigned as per the registered client configuration","path":"/api/oidc/userinfo","remote_ip":"<server_ip>","time":"2025-09-23T13:06:34+02:00"} {"level":"debug","method":"GET","msg":"User Info Request with id 'd21b1ddf-319d-41db-83e6-bf142b9bfed9' on client with id '<secure_id>' was successfully processed","path":"/api/oidc/userinfo","remote_ip":"<server_ip>","time":"2025-09-23T13:06:34+02:00"} {"level":"debug","msg":"Check authorization of subject username= groups= ip=<external_ip> and object https://budget.domain.tld/favicon.ico (method GET).","time":"2025-09-23T13:06:34+02:00"} ``` As far as I can see, the entire OIDC flow completed cleanly, however AB logs a 400 response afterwards: ``` 2025-09-23T11:05:17.418Z info: POST 200 /account/login 2025-09-23T11:06:34.295Z info: GET 400 /openid/callback?code=<authorization_code>&iss=https%3A%2F%2Fauth.domain.tld&scope=openid+email+profile&state=<state> ``` Obviously I don't know anything about AB's internal OIDC flow, but it seems to me that after login, it's making a second request to its own callback endpoint with the **same code that Authelia already consumed**. Here's Authelia's OIDC configuration for the Actual Budget client, for posterity: ``` - client_id: 'secure_id' client_name: 'Actual Budget' client_secret: 'hashed_secure_secret public: false authorization_policy: 'one_factor' require_pkce: false pkce_challenge_method: '' redirect_uris: - 'https://budget.domain.tld/openid/callback' scopes: - 'openid' - 'profile' - 'groups' - 'email' response_types: - 'code' grant_types: - 'authorization_code' access_token_signed_response_alg: 'none' userinfo_signed_response_alg: 'none' token_endpoint_auth_method: 'client_secret_basic' ```
Author
Owner

@Cheezzhead commented on GitHub (Sep 24, 2025):

Update: I've restarted AB with a fresh config and empty sqlite database. I've also assigned a new client id to the respective OIDC client.

I'm able to log in with the server admin account so that this account becomes the server owner. However, as soon as I try to log in with any other account, the issue persists.

@Cheezzhead commented on GitHub (Sep 24, 2025): Update: I've restarted AB with a fresh config and empty sqlite database. I've also assigned a new client id to the respective OIDC client. I'm able to log in with the server admin account so that this account becomes the server owner. However, as soon as I try to log in with any other account, the issue persists.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/actual#2485