Skip to content

Auth Code flow with incorrect PKCE code_challenge works correctly on the 2nd attempt #815

@micolous

Description

@micolous

Edit (2025-03-21): I've discovered a mistake in my PKCE implementation: the code_challenge value included Base64 padding. Removing that padding fixes the issue, but it looks like mock-oauth2-server accepts incorrect code_challenge values on the 2nd attempt, and there are some usability improvements it could do.

Original report

I'm using mock-oauth2-server in its Docker container to test the server side of a Python application server which is a token-mediating backend + resource server hybrid for a browser-based single-page application.

When attempting to acquire a token with the Authorisation Code flow with PKCE (as a public client), mock-oauth2-server only works correctly on the 2nd attempt.

I've made a script which replicates a minimal subset of my server's test suite and auth logic to demonstrate the issue, which sends 5 identical token requests, but with their own cookie jars (to simulate multiple API calls): https://gist.github.com/micolous/b9c5bbd2964bd5488550a9bda12ece9c

Expected behaviour

The first (and some number of following) attempt(s) works, and all subsequent attempts (perhaps after a delay) fail.

Allowing more than one request to work correctly may be helpful (see additional notes).

Actual behaviour

On the 1st attempt, mock-oauth2-server returns:

{'error_description': 'invalid_pkce: code_verifier does not compute to code_challenge from request',
 'error': 'invalid_grant'}

The 2nd attempt works correctly, with all claims set as configured.

On the 3rd and following attempts, mock-oauth2-server returns a valid but incorrect access_token and id_token, where it:

  • sets sub to a random UUID, rather than the value supplied in the username parameter to the login form
  • drops any parameters passed via the claims parameter to the login form
  • drops any parameters passed via the tokenCallbacks[].requestMappings[].claims config option

I suspect mock-oauth2-server is stashing some state from the /authorize request, but then it can't fetch it on the 1st attempt, and then deletes it after the 2nd attempt.

Workarounds

I've made my server side retry any token request twice, and fail if id_token is missing claims.

Additional notes

I never noticed this issue when testing with the SPA, because React's strict mode causes useEffect to run twice (and thus, any API call in a useEffect would also trigger twice). So the first call it made to complete the auth process would fail, but then the second request would be fine.

When I worked around this by adding a retry to the server side, React was still making two attempts, and that's where I found that requesting a token from mock-oauth2-server a 3rd time returns a token without any of the claims I expected.

Environment

mock-oauth2-server 2.1.10 using the Docker container on linux/aarch64, with this config:

{
  "httpServer": {
    "type": "NettyWrapper",
    "ssl": {
      "keyPassword": "",
      "keystoreFile": "/run/secrets/server_p12",
      "keystoreType": "PKCS12",
      "keystorePassword": ""
    }
  }
}

The keystore file is provided via Docker Secrets.

The example script will accept any certificate it is given. There is a commented out option to validate certificates properly.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions