Skip to content

DPoP client-credentials flow sends a proofless token request first — extra round trip + failed grant in the org System Log #598

Description

@turkdogan

Describe the bug?

When using private-key (PrivateKeyAuth / JWKAuth) or JWTAuth client-credentials auth against an Okta org with Require DPoP enabled, the SDK acquires tokens reactively: it sends the first POST /oauth2/v1/token without a DPoP proof, lets Okta reject it with invalid_dpop_proof, then retries with a proof.

Per RFC 9449 a DPoP-capable client should attach the proof proactively. Because of the reactive approach, every token acquisition on this path:

  • makes an extra round trip (3 requests to /oauth2/v1/token instead of 2), and
  • records a failed token grant in the org System Log (the rejected proofless attempt) on every acquisition — recurring noise/false alarms for admins monitoring auth failures.

What is expected to happen?

The SDK attaches a DPoP proof to the first token request (DPoP-first). No proofless request is sent, no failed grant is logged, and a DPoP-required app completes in 2 requests (use_dpop_nonce challenge + token). Non-DPoP apps are unaffected.

What is the actual behavior?

The first token request carries no DPoP proof and is rejected with invalid_dpop_proof; the SDK then retries with a proof. The proofless rejection is logged as a failed token grant in the org System Log before each successful DPoP-bound grant, and adds an extra round trip.

Reproduction Steps?

  1. Create an Okta OAuth service app with a private key (JWK), scoped for client-credentials, and enable Require Demonstrating Proof of Possession (DPoP) on it.
  2. Configure the Go SDK with AuthorizationMode: "PrivateKey" (or JWK) using that key.
  3. Make any API call so the SDK fetches a token.
  4. In the org System Log, observe a failed invalid_dpop_proof token grant immediately before each successful DPoP-bound grant; a network capture shows 3 requests to /oauth2/v1/token instead of 2.

Additional Information?

Proposed fix: #597 (DPoP-first — pre-generate one key, attach a proof to the first token request, reuse it across the use_dpop_nonce retry, and adapt to Bearer vs DPoP from the response). Affects all v6 releases (the reactive getAccessTokenForDpopPrivateKey flow). No public API change; non-DPoP apps unaffected.

Golang Version

go version go1.26.4 darwin/arm64 (behavior is version-independent)

SDK Version

v6.1.x (present throughout v6)

OS version

macOS (Darwin, arm64)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions