Skip to content

OAuth flow token exchange issue #294

@nackko

Description

@nackko

Hello, following this conversation, I'm opening this issue for a potential bug in the OAuth flow.

Note

With help from thisismissem.social, I found a workable solution more in line with the intended flow design so all is good this is not an issue for me.

Context

  • native Android client (Compose Multiplatform)
  • https redirect uri
  • custom html to extract code and state

Issue

The app is successful in getting an authorization code from the pds but unable to exchange it for credential tokens.

metadata JSON file

{
  "client_id": "https://auth.ontempo.social/native/metadata/oauth-client-metadata.json",
  "application_type": "native",
  "response_types": ["code"],
  "grant_types": ["authorization_code", "refresh_token"],
  "dpop_bound_access_tokens": true,
  "redirect_uris": ["https://auth.ontempo.social/native/callback/callback.html"],
  "scope": "atproto transition:generic",
  "token_endpoint_auth_method": "none"
}

callback html file

<!-- place this file in ${DOCKER_CONFIG_DIR}/**/auth/native/callback and delete this line -->
<!DOCTYPE html>
<html>
<head>
    <title>Tempo OAuth Callback</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<h1>Redirecting to app...</h1>
<p>If the app doesn't open automatically, please return to the app manually.</p>
<script>
    // Extract all parameters from URL
    const urlParams = new URLSearchParams(window.location.search);
    const code = urlParams.get('code');
    const state = urlParams.get('state');
    const iss = urlParams.get('iss');
    const error = urlParams.get('error');

    if (code && state) {
        // Build redirect URL with all parameters
        let redirectUrl = `co.ontempo.tempo://oauth/callback?code=${code}&state=${state}`;
        if (iss) {
            redirectUrl += `&iss=${encodeURIComponent(iss)}`;
        }
        window.location.href = redirectUrl;
    } else if (error) {
        window.location.href = `co.ontempo.tempo://oauth/callback?error=${error}`;
    }
</script>
</body>
</html>

Application log

2025-12-09 22:14:07.117 26589-26589 AtProtoAuth             co.ontempo.tempo                     I  Authentication started for handle: nackko.ontempo.social
2025-12-09 22:14:07.143 26589-26672 HTTP                    co.ontempo.tempo                     D  REQUEST: https://nackko.ontempo.social/.well-known/atproto-did
                                                                                                    METHOD: GET
                                                                                                    COMMON HEADERS
                                                                                                    -> Accept: application/json
                                                                                                    -> Accept-Charset: UTF-8
                                                                                                    CONTENT HEADERS
                                                                                                    -> Content-Length: 0
                                                                                                    BODY Content-Type: null
                                                                                                    BODY START
                                                                                                    
                                                                                                    BODY END
2025-12-09 22:14:07.242 26589-26589 HTTP                    co.ontempo.tempo                     D  RESPONSE: 200 OK
                                                                                                    METHOD: GET
                                                                                                    FROM: https://nackko.ontempo.social/.well-known/atproto-did
                                                                                                    COMMON HEADERS
                                                                                                    -> access-control-allow-origin: *
                                                                                                    -> connection: keep-alive
                                                                                                    -> content-length: 32
                                                                                                    -> content-type: text/plain; charset=utf-8
                                                                                                    -> date: Wed, 10 Dec 2025 03:14:05 GMT
                                                                                                    -> etag: W/"20-4T29UDzW0hknFhntyFodS7z/46g"
                                                                                                    -> server: openresty
                                                                                                    -> strict-transport-security: max-age=63072000; preload
                                                                                                    -> vary: Accept-Encoding
                                                                                                    -> x-android-received-millis: 1765336447230
                                                                                                    -> x-android-response-source: NETWORK 200
                                                                                                    -> x-android-selected-protocol: http/1.1
                                                                                                    -> x-android-sent-millis: 1765336447216
                                                                                                    -> x-powered-by: Express
                                                                                                    -> x-served-by: nackko.ontempo.social
                                                                                                    BODY Content-Type: text/plain; charset=utf-8
                                                                                                    BODY START
                                                                                                    did:plc:syldnchxgxctqcjvkhqprrxz
                                                                                                    BODY END
2025-12-09 22:14:07.245 26589-26589 AtProtoAuth             co.ontempo.tempo                     D  Resolved handle to DID: did:plc:syldnchxgxctqcjvkhqprrxz
2025-12-09 22:14:07.248 26589-26673 HTTP                    co.ontempo.tempo                     D  REQUEST: https://plc.directory/did:plc:syldnchxgxctqcjvkhqprrxz
                                                                                                    METHOD: GET
                                                                                                    COMMON HEADERS
                                                                                                    -> Accept: application/json
                                                                                                    -> Accept-Charset: UTF-8
                                                                                                    CONTENT HEADERS
                                                                                                    -> Content-Length: 0
                                                                                                    BODY Content-Type: null
                                                                                                    BODY START
                                                                                                    
                                                                                                    BODY END
2025-12-09 22:14:07.401 26589-26589 HTTP                    co.ontempo.tempo                     D  RESPONSE: 200 OK
                                                                                                    METHOD: GET
                                                                                                    FROM: https://plc.directory/did:plc:syldnchxgxctqcjvkhqprrxz
                                                                                                    COMMON HEADERS
                                                                                                    -> access-control-allow-origin: *
                                                                                                    -> connection: keep-alive
                                                                                                    -> content-length: 551
                                                                                                    -> content-type: application/did+ld+json; charset=utf-8
                                                                                                    -> date: Wed, 10 Dec 2025 03:14:05 GMT
                                                                                                    -> etag: W/"227-Syo10B6ojzWOYsxvvh75KHU1YPc"
                                                                                                    -> x-android-received-millis: 1765336447393
                                                                                                    -> x-android-response-source: NETWORK 200
                                                                                                    -> x-android-selected-protocol: http/1.1
                                                                                                    -> x-android-sent-millis: 1765336447329
                                                                                                    -> x-powered-by: Express
                                                                                                    BODY Content-Type: application/did+ld+json; charset=utf-8
                                                                                                    BODY START
                                                                                                    {"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/multikey/v1","https://w3id.org/security/suites/secp256k1-2019/v1"],"id":"did:plc:syldnchxgxctqcjvkhqprrxz","alsoKnownAs":["at://nackko.ontempo.social"],"verificationMethod":[{"id":"did:plc:syldnchxgxctqcjvkhqprrxz#atproto","type":"Multikey","controller":"did:plc:syldnchxgxctqcjvkhqprrxz","publicKeyMultibase":"zQ3shfXLMEimkQEZL48y2ouKh9KXEnqYhdhtCJmjk5N1fNjqH"}],"service":[{"id":"#atproto_pds","type":"AtprotoPersonalDataServer","serviceEndpoint":"https://pds.ontempo.social"}]}
                                                                                                    BODY END
2025-12-09 22:14:07.413 26589-26589 AtProtoAuth             co.ontempo.tempo                     D  Retrieved PDS URL: https://pds.ontempo.social
2025-12-09 22:14:07.415 26589-26673 HTTP                    co.ontempo.tempo                     D  REQUEST: https://pds.ontempo.social/.well-known/oauth-authorization-server
                                                                                                    METHOD: GET
                                                                                                    COMMON HEADERS
                                                                                                    -> Accept: application/json
                                                                                                    -> Accept-Charset: UTF-8
                                                                                                    CONTENT HEADERS
                                                                                                    -> Content-Length: 0
                                                                                                    BODY Content-Type: null
                                                                                                    BODY START
                                                                                                    
                                                                                                    BODY END
2025-12-09 22:14:07.489 26589-26589 HTTP                    co.ontempo.tempo                     D  RESPONSE: 200 OK
                                                                                                    METHOD: GET
                                                                                                    FROM: https://pds.ontempo.social/.well-known/oauth-authorization-server
                                                                                                    COMMON HEADERS
                                                                                                    -> access-control-allow-headers: Content-Type,DPoP
                                                                                                    -> access-control-allow-methods: *
                                                                                                    -> access-control-allow-origin: *
                                                                                                    -> access-control-max-age: 86400
                                                                                                    -> cache-control: max-age=300
                                                                                                    -> connection: keep-alive
                                                                                                    -> content-type: application/json
                                                                                                    -> date: Wed, 10 Dec 2025 03:14:05 GMT
                                                                                                    -> server: openresty
                                                                                                    -> strict-transport-security: max-age=63072000; preload
                                                                                                    -> transfer-encoding: chunked
                                                                                                    -> vary: Accept-Encoding
                                                                                                    -> x-android-received-millis: 1765336447481
                                                                                                    -> x-android-response-source: NETWORK 200
                                                                                                    -> x-android-selected-protocol: http/1.1
                                                                                                    -> x-android-sent-millis: 1765336447467
                                                                                                    -> x-powered-by: Express
                                                                                                    -> x-served-by: pds.ontempo.social
                                                                                                    BODY Content-Type: application/json
                                                                                                    BODY START
                                                                                                    {"issuer":"https://ontempo.social","request_parameter_supported":true,"request_uri_parameter_supported":true,"require_request_uri_registration":true,"scopes_supported":["atproto","transition:email","transition:generic","transition:chat.bsky"],"subject_types_supported":["public"],"response_types_supported":["code"],"response_modes_supported":["query","fragment","form_post"],"grant_types_supported":["authorization_code","refresh_token"],"code_challenge_methods_supported":["S256"],"ui_locales_supported":["en-US"],"display_values_supported":["page","popup","touch"],"request_object_signing_alg_values_supported":["RS256","RS384","RS512","PS256","PS384","PS512","ES256","ES256K","ES384","ES512","none"],"authorization_response_iss_parameter_supported":true,"request_object_encryption_alg_values_supported":[],"request_object_encryption_enc_values_supported":[],"jwks_uri":"https://ontempo.social/oauth/jwks","authorization_endpoint":"https://ontempo.social/oauth/authorize","token_endpoint":"https://ontempo.social/oauth/token","token_endpoint_auth_methods_supported":["none","private_key_jwt"],"token_endpoint_auth_signing_alg_values_supported":["RS256","RS384","RS512","PS256","PS384","PS512","ES256","ES256K","ES384","ES512"],"revocation_endpoint":"https://ontempo.social/oauth/revoke","pushed_authorization_request_endpoint":"https://ontempo.social/oauth/par","require_pushed_authorization_requests":true,"dpop_signing_alg_values_supported":["RS256","RS384","RS512","PS256","PS384","PS512","ES256","ES256K","ES384","ES512"],"protected_resources":["https://ontempo.social"],"client_id_metadata_document_supported":true}
                                                                                                    BODY END
2025-12-09 22:14:07.494 26589-26589 AtProtoAuth             co.ontempo.tempo                     D  Retrieved OAuth metadata from issuer: https://ontempo.social
2025-12-09 22:14:07.500 26589-26589 AtProtoAuth             co.ontempo.tempo                     D  Generated DPoP key pair (keyId: kzbcqvp3yp7Z_RSh)
2025-12-09 22:14:07.500 26589-26589 System.out              co.ontempo.tempo                     I  Generated code_verifier: length=43
2025-12-09 22:14:07.501 26589-26589 AtProtoAuth             co.ontempo.tempo                     D  Initiating PAR request to https://ontempo.social/oauth/par
2025-12-09 22:14:07.513 26589-26673 HTTP                    co.ontempo.tempo                     D  REQUEST: https://ontempo.social/oauth/par
                                                                                                    METHOD: POST
                                                                                                    COMMON HEADERS
                                                                                                    -> Accept: application/json
                                                                                                    -> Accept-Charset: UTF-8
                                                                                                    -> DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJ4IjoiWDhuVEtVNXJreHpMWGMzUzRwaUs4clNBWEd4Q252STIyYWFtVHRhWW5LMCIsInkiOiJqOGNQYXFwdDc4X0hxT3hqNTU2cWpqbTJ6QVpaSzVLN2d2UmlBQTJWT3gwIn19.eyJodG0iOiJQT1NUIiwiaHR1IjoiaHR0cHM6Ly9vbnRlbXBvLnNvY2lhbC9vYXV0aC9wYXIiLCJpYXQiOjE3NjUzMzY0NDcsImp0aSI6Ijk1Nzc2M2EyYjIzMDk1NjYxOTYwNzVkY2NjZjFmYzEzIn0.lSMPhraU1iwPEKfhFZnRqU7jJbr7E2uYrRWq5qq4H_9nF2iiQnH5JDrWHXF3py98Nhftit0PXjeJBZqzlUO_1g
                                                                                                    CONTENT HEADERS
                                                                                                    -> Content-Length: 387
                                                                                                    -> Content-Type: application/x-www-form-urlencoded; charset=UTF-8
                                                                                                    BODY Content-Type: application/x-www-form-urlencoded; charset=UTF-8
                                                                                                    BODY START
                                                                                                    client_id=https%3A%2F%2Fauth.ontempo.social%2Fnative%2Fmetadata%2Foauth-client-metadata.json&code_challenge=J6BWB34eA_TK3vBqeJVJxjvIBvBF3jUm9rKeXP3fyII&code_challenge_method=S256&redirect_uri=https%3A%2F%2Fauth.ontempo.social%2Fnative%2Fcallback%2Fcallback.html&response_type=code&scope=atproto+transition%3Ageneric&state=3c3a8b89a89f6d85d8aeb63f2d9198e5&login_hint=nackko.ontempo.social
                                                                                                    BODY END
2025-12-09 22:14:07.585 26589-26589 HTTP                    co.ontempo.tempo                     D  RESPONSE: 400 Bad Request
                                                                                                    METHOD: POST
                                                                                                    FROM: https://ontempo.social/oauth/par
                                                                                                    COMMON HEADERS
                                                                                                    -> access-control-allow-headers: Content-Type,DPoP
                                                                                                    -> access-control-allow-methods: *
                                                                                                    -> access-control-allow-origin: *
                                                                                                    -> access-control-expose-headers: DPoP-Nonce
                                                                                                    -> access-control-max-age: 86400
                                                                                                    -> cache-control: no-store
                                                                                                    -> connection: keep-alive
                                                                                                    -> content-type: application/json
                                                                                                    -> date: Wed, 10 Dec 2025 03:14:05 GMT
                                                                                                    -> dpop-nonce: _aI36T_afmH32_vOPANVF9PTt1WtG0OSEIwr1TgG0qA
                                                                                                    -> pragma: no-cache
                                                                                                    -> server: openresty
                                                                                                    -> strict-transport-security: max-age=63072000; preload
                                                                                                    -> transfer-encoding: chunked
                                                                                                    -> vary: Accept-Encoding
                                                                                                    -> x-android-received-millis: 1765336447577
                                                                                                    -> x-android-response-source: NETWORK 400
                                                                                                    -> x-android-selected-protocol: http/1.1
                                                                                                    -> x-android-sent-millis: 1765336447565
                                                                                                    -> x-powered-by: Express
                                                                                                    BODY Content-Type: application/json
                                                                                                    BODY START
                                                                                                    {"error":"use_dpop_nonce","error_description":"Authorization server requires nonce in DPoP proof"}
                                                                                                    BODY END
2025-12-09 22:14:07.588 26589-26674 HTTP                    co.ontempo.tempo                     D  REQUEST: https://ontempo.social/oauth/par
                                                                                                    METHOD: POST
                                                                                                    COMMON HEADERS
                                                                                                    -> Accept: application/json
                                                                                                    -> Accept-Charset: UTF-8
                                                                                                    -> DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJ4IjoiWDhuVEtVNXJreHpMWGMzUzRwaUs4clNBWEd4Q252STIyYWFtVHRhWW5LMCIsInkiOiJqOGNQYXFwdDc4X0hxT3hqNTU2cWpqbTJ6QVpaSzVLN2d2UmlBQTJWT3gwIn19.eyJodG0iOiJQT1NUIiwiaHR1IjoiaHR0cHM6Ly9vbnRlbXBvLnNvY2lhbC9vYXV0aC9wYXIiLCJpYXQiOjE3NjUzMzY0NDcsImp0aSI6Ijk4YTQxZGNmYWI0MThhZWMyNDNjYzEyYTg0ZGM4ZmJmIiwibm9uY2UiOiJfYUkzNlRfYWZtSDMyX3ZPUEFOVkY5UFR0MVd0RzBPU0VJd3IxVGdHMHFBIn0.oWMNNbJoaOk5LIggFGTFZdnaRVnr4NEexj14qTD2kS-PbKwU82vAC6phBupf24gEPW3xLO8MHWgihUqVMAgEWA
                                                                                                    CONTENT HEADERS
                                                                                                    -> Content-Length: 387
                                                                                                    -> Content-Type: application/x-www-form-urlencoded; charset=UTF-8
                                                                                                    BODY Content-Type: application/x-www-form-urlencoded; charset=UTF-8
                                                                                                    BODY START
                                                                                                    client_id=https%3A%2F%2Fauth.ontempo.social%2Fnative%2Fmetadata%2Foauth-client-metadata.json&code_challenge=J6BWB34eA_TK3vBqeJVJxjvIBvBF3jUm9rKeXP3fyII&code_challenge_method=S256&redirect_uri=https%3A%2F%2Fauth.ontempo.social%2Fnative%2Fcallback%2Fcallback.html&response_type=code&scope=atproto+transition%3Ageneric&state=3c3a8b89a89f6d85d8aeb63f2d9198e5&login_hint=nackko.ontempo.social
                                                                                                    BODY END
2025-12-09 22:14:07.609 26589-26589 HTTP                    co.ontempo.tempo                     D  RESPONSE: 201 Created
                                                                                                    METHOD: POST
                                                                                                    FROM: https://ontempo.social/oauth/par
                                                                                                    COMMON HEADERS
                                                                                                    -> access-control-allow-headers: Content-Type,DPoP
                                                                                                    -> access-control-allow-methods: *
                                                                                                    -> access-control-allow-origin: *
                                                                                                    -> access-control-expose-headers: DPoP-Nonce
                                                                                                    -> access-control-max-age: 86400
                                                                                                    -> cache-control: no-store
                                                                                                    -> connection: keep-alive
                                                                                                    -> content-type: application/json
                                                                                                    -> date: Wed, 10 Dec 2025 03:14:05 GMT
                                                                                                    -> dpop-nonce: _aI36T_afmH32_vOPANVF9PTt1WtG0OSEIwr1TgG0qA
                                                                                                    -> pragma: no-cache
                                                                                                    -> server: openresty
                                                                                                    -> strict-transport-security: max-age=63072000; preload
                                                                                                    -> transfer-encoding: chunked
                                                                                                    -> vary: Accept-Encoding
                                                                                                    -> x-android-received-millis: 1765336447604
                                                                                                    -> x-android-response-source: NETWORK 201
                                                                                                    -> x-android-selected-protocol: http/1.1
                                                                                                    -> x-android-sent-millis: 1765336447592
                                                                                                    -> x-powered-by: Express
                                                                                                    -> x-served-by: ontempo.social
                                                                                                    BODY Content-Type: application/json
                                                                                                    BODY START
                                                                                                    {"request_uri":"urn:ietf:params:oauth:request_uri:req-1db1d3f35c68a33670345c02c52473b8","expires_in":299}
                                                                                                    BODY END
2025-12-09 22:14:07.612 26589-26589 AtProtoAuth             co.ontempo.tempo                     D  PAR request successful, received request_uri
2025-12-09 22:14:07.612 26589-26589 System.out              co.ontempo.tempo                     I  Authorization URL: https://ontempo.social/oauth/authorize?client_id=https://auth.ontempo.social/native/metadata/oauth-client-metadata.json&request_uri=urn:ietf:params:oauth:request_uri:req-1db1d3f35c68a33670345c02c52473b8
2025-12-09 22:14:07.612 26589-26589 AtProtoAuth             co.ontempo.tempo                     D  Opening browser for user authorization
2025-12-09 22:14:07.769 26589-26589 ImeBackDispatcher       co.ontempo.tempo                     D  Clear (mImeCallbacks.size=0)
2025-12-09 22:14:07.769 26589-26589 ImeBackDispatcher       co.ontempo.tempo                     D  switch root view (mImeCallbacks.size=0)
2025-12-09 22:14:08.216 26589-26589 VRI[MainActivity]       co.ontempo.tempo                     D  visibilityChanged oldVisibility=true newVisibility=false
2025-12-09 22:14:08.229 26589-26605 HWUI                    co.ontempo.tempo                     D  endAllActiveAnimators on 0xb4000071d7e56ee0 (UnprojectedRipple) with handle 0xb4000070a7e6c4e0
2025-12-09 22:14:08.245 26589-26589 AutofillManager         co.ontempo.tempo                     I  onInvisibleForAutofill(): expiringResponse
2025-12-09 22:14:12.286 26589-26589 AtProtoAuth             co.ontempo.tempo                     D  Received redirect from authorization server
2025-12-09 22:14:12.286 26589-26589 AtProtoAuth             co.ontempo.tempo                     D  State validation successful
2025-12-09 22:14:12.286 26589-26589 AtProtoAuth             co.ontempo.tempo                     D  === Authorization code extracted from redirect === cod-aff4afba08ecb564493411803208257eeff1853e4092320047a101b70cda2a9e
2025-12-09 22:14:12.286 26589-26589 AtProtoAuth             co.ontempo.tempo                     D  Exchanging authorization code for tokens
2025-12-09 22:14:12.292 26589-26674 HTTP                    co.ontempo.tempo                     D  REQUEST: https://ontempo.social/oauth/token
                                                                                                    METHOD: POST
                                                                                                    COMMON HEADERS
                                                                                                    -> Accept: application/json
                                                                                                    -> Accept-Charset: UTF-8
                                                                                                    -> DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJ4IjoiWDhuVEtVNXJreHpMWGMzUzRwaUs4clNBWEd4Q252STIyYWFtVHRhWW5LMCIsInkiOiJqOGNQYXFwdDc4X0hxT3hqNTU2cWpqbTJ6QVpaSzVLN2d2UmlBQTJWT3gwIn19.eyJodG0iOiJQT1NUIiwiaHR1IjoiaHR0cHM6Ly9vbnRlbXBvLnNvY2lhbC9vYXV0aC90b2tlbiIsImlhdCI6MTc2NTMzNjQ1MiwianRpIjoiMDZiNTgzYWU2NDM4MDkwMDMzNzIzYWI0ODlmNjNmNDMifQ.vwQIYg4cI2X_MK5N4WPwqsJyxVIu7g8H4QGDP1MmZ1-SZE3btWR0L2BrPJ5HfLBLM06kB6BqcwG1KCSP6F-_6g
                                                                                                    CONTENT HEADERS
                                                                                                    -> Content-Length: 337
                                                                                                    -> Content-Type: application/x-www-form-urlencoded; charset=UTF-8
                                                                                                    BODY Content-Type: application/x-www-form-urlencoded; charset=UTF-8
                                                                                                    BODY START
                                                                                                    grant_type=authorization_code&code=cod-aff4afba08ecb564493411803208257eeff1853e4092320047a101b70cda2a9e&code_verifier=OlhBuzK9zGTv9FEtAWJ9shdUD4_YEWLRjMaCtHwqMmY&redirect_uri=https%3A%2F%2Fauth.ontempo.social%2Fnative%2Fcallback%2Fcallback.html&client_id=https%3A%2F%2Fauth.ontempo.social%2Fnative%2Fmetadata%2Foauth-client-metadata.json
                                                                                                    BODY END
2025-12-09 22:14:12.304 26589-26589 ViewRootImpl            co.ontempo.tempo                     D  Skipping stats log for color mode
2025-12-09 22:14:12.323 26589-26589 HTTP                    co.ontempo.tempo                     D  RESPONSE: 400 Bad Request
                                                                                                    METHOD: POST
                                                                                                    FROM: https://ontempo.social/oauth/token
                                                                                                    COMMON HEADERS
                                                                                                    -> access-control-allow-headers: Content-Type,DPoP
                                                                                                    -> access-control-allow-methods: *
                                                                                                    -> access-control-allow-origin: *
                                                                                                    -> access-control-expose-headers: DPoP-Nonce
                                                                                                    -> access-control-max-age: 86400
                                                                                                    -> cache-control: no-store
                                                                                                    -> connection: keep-alive
                                                                                                    -> content-type: application/json
                                                                                                    -> date: Wed, 10 Dec 2025 03:14:10 GMT
                                                                                                    -> dpop-nonce: _aI36T_afmH32_vOPANVF9PTt1WtG0OSEIwr1TgG0qA
                                                                                                    -> pragma: no-cache
                                                                                                    -> server: openresty
                                                                                                    -> strict-transport-security: max-age=63072000; preload
                                                                                                    -> transfer-encoding: chunked
                                                                                                    -> vary: Accept-Encoding
                                                                                                    -> x-android-received-millis: 1765336452308
                                                                                                    -> x-android-response-source: NETWORK 400
                                                                                                    -> x-android-selected-protocol: http/1.1
                                                                                                    -> x-android-sent-millis: 1765336452299
                                                                                                    -> x-powered-by: Express
                                                                                                    BODY Content-Type: application/json
                                                                                                    BODY START
                                                                                                    {"error":"use_dpop_nonce","error_description":"Authorization server requires nonce in DPoP proof"}
                                                                                                    BODY END
2025-12-09 22:14:12.328 26589-26673 HTTP                    co.ontempo.tempo                     D  REQUEST: https://ontempo.social/oauth/token
                                                                                                    METHOD: POST
                                                                                                    COMMON HEADERS
                                                                                                    -> Accept: application/json
                                                                                                    -> Accept-Charset: UTF-8
                                                                                                    -> DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJ4IjoiWDhuVEtVNXJreHpMWGMzUzRwaUs4clNBWEd4Q252STIyYWFtVHRhWW5LMCIsInkiOiJqOGNQYXFwdDc4X0hxT3hqNTU2cWpqbTJ6QVpaSzVLN2d2UmlBQTJWT3gwIn19.eyJodG0iOiJQT1NUIiwiaHR1IjoiaHR0cHM6Ly9vbnRlbXBvLnNvY2lhbC9vYXV0aC90b2tlbiIsImlhdCI6MTc2NTMzNjQ1MiwianRpIjoiNzlkMjZlYjVlOTg2YjMwZGFmZDVkMDUxODdhN2NlMjMiLCJub25jZSI6Il9hSTM2VF9hZm1IMzJfdk9QQU5WRjlQVHQxV3RHME9TRUl3cjFUZ0cwcUEifQ.f_xU1HO-rnVRkxZzqMj7gq-Qa8pW6Swm1jh2UbBLVZXU6Ykc8eX0Bqhil7j6uFICvCMDF3LaaxVqbN6iJ1031g
                                                                                                    CONTENT HEADERS
                                                                                                    -> Content-Length: 337
                                                                                                    -> Content-Type: application/x-www-form-urlencoded; charset=UTF-8
                                                                                                    BODY Content-Type: application/x-www-form-urlencoded; charset=UTF-8
                                                                                                    BODY START
                                                                                                    grant_type=authorization_code&code=cod-aff4afba08ecb564493411803208257eeff1853e4092320047a101b70cda2a9e&code_verifier=OlhBuzK9zGTv9FEtAWJ9shdUD4_YEWLRjMaCtHwqMmY&redirect_uri=https%3A%2F%2Fauth.ontempo.social%2Fnative%2Fcallback%2Fcallback.html&client_id=https%3A%2F%2Fauth.ontempo.social%2Fnative%2Fmetadata%2Foauth-client-metadata.json
                                                                                                    BODY END
2025-12-09 22:14:12.353 26589-26589 HTTP                    co.ontempo.tempo                     D  RESPONSE: 400 Bad Request
                                                                                                    METHOD: POST
                                                                                                    FROM: https://ontempo.social/oauth/token
                                                                                                    COMMON HEADERS
                                                                                                    -> access-control-allow-headers: Content-Type,DPoP
                                                                                                    -> access-control-allow-methods: *
                                                                                                    -> access-control-allow-origin: *
                                                                                                    -> access-control-expose-headers: DPoP-Nonce
                                                                                                    -> access-control-max-age: 86400
                                                                                                    -> cache-control: no-store
                                                                                                    -> connection: keep-alive
                                                                                                    -> content-type: application/json
                                                                                                    -> date: Wed, 10 Dec 2025 03:14:10 GMT
                                                                                                    -> dpop-nonce: _aI36T_afmH32_vOPANVF9PTt1WtG0OSEIwr1TgG0qA
                                                                                                    -> pragma: no-cache
                                                                                                    -> server: openresty
                                                                                                    -> strict-transport-security: max-age=63072000; preload
                                                                                                    -> transfer-encoding: chunked
                                                                                                    -> vary: Accept-Encoding
                                                                                                    -> x-android-received-millis: 1765336452347
                                                                                                    -> x-android-response-source: NETWORK 400
                                                                                                    -> x-android-selected-protocol: http/1.1
                                                                                                    -> x-android-sent-millis: 1765336452334
                                                                                                    -> x-powered-by: Express
                                                                                                    BODY Content-Type: application/json
                                                                                                    BODY START
                                                                                                    {"error":"invalid_grant","error_description":"Invalid code"}
                                                                                                    BODY END
2025-12-09 22:14:12.356 26589-26589 HTTP                    co.ontempo.tempo                     D  RESPONSE https://ontempo.social/oauth/token failed with exception: io.ktor.serialization.JsonConvertException: Illegal input: Fields [access_token, refresh_token, expires_in, scope, sub] are required for type with serial name 'co.ontempo.tempo.framework.authentication.dto.AtProtoCredentialsDto', but they were missing at path: $
2025-12-09 22:14:12.356 26589-26589 AtProtoAuth             co.ontempo.tempo                     E  Authentication failed for handle: nackko.ontempo.social
                                                                                                    java.lang.Exception: Illegal input: Fields [access_token, refresh_token, expires_in, scope, sub] are required for type with serial name 'co.ontempo.tempo.framework.authentication.dto.AtProtoCredentialsDto', but they were missing at path: $

While I was banging my head, I tried to copy the pds database after the code issuance but before trying to exchange it and got confused as to why the code was not in the database. Here is a summary of my findings with Claude (I am not a db guy hehe)

Investigation Steps Performed

Test 1: Database State Before Authentication

SELECT COUNT(*) FROM authorization_request;
-- Result: 2 (old expired requests)

SELECT COUNT(*) FROM token;
-- Result: 2

SELECT COUNT(*) FROM device_account;
-- Result: 0

Test 2: Database State After Code Received

Immediately after receiving authorization code cod-909e11ef897e3a4e589c228d7400da93a7e40c9044ceff559de6f3517ecb8b10:

SELECT * FROM authorization_request WHERE code = 'cod-909e...';
-- Result: EMPTY

SELECT * FROM token WHERE code = 'cod-909e...';
-- Result: EMPTY

SELECT COUNT(*) FROM authorization_request;
-- Result: 0 (old requests cleaned up)

SELECT COUNT(*) FROM device_account;
-- Result: 0 (still empty!)

Test 3: Evidence of PDS Activity

SELECT * FROM authorized_client 
WHERE clientId = 'https://auth.ontempo.social/native/metadata/client-metadata.json';
-- Result: Record exists with updatedAt: 2025-12-01T03:18:20.801Z (during authentication attempt)

SELECT * FROM device ORDER BY lastSeenAt DESC LIMIT 1;
-- Result: dev-62584ad3b9be1725075f4e55dce5a74b with lastSeenAt: 2025-12-01T03:18:19.101Z

SELECT * FROM device_account WHERE deviceId = 'dev-62584ad3b9be1725075f4e55dce5a74b';
-- Result: EMPTY

Test 4: Account and Actor Verification

SELECT * FROM account WHERE did = 'did:plc:syldnchxgxctqcjvkhqprrxz';
-- Result: Account exists with email nackko@***

SELECT * FROM actor WHERE did = 'did:plc:syldnchxgxctqcjvkhqprrxz';
-- Result: Actor exists with handle nackko.ontempo.social

Again I got help from the Discord so feel free to close this issue if it's not relevant.

Thks for all the great work!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions