Skip to content

fix: attempt DPoP up front in private-key token flow - #597

Open
turkdogan wants to merge 1 commit into
okta:masterfrom
turkdogan:fix/dpop-first-private-key-auth
Open

fix: attempt DPoP up front in private-key token flow#597
turkdogan wants to merge 1 commit into
okta:masterfrom
turkdogan:fix/dpop-first-private-key-auth

Conversation

@turkdogan

@turkdogan turkdogan commented Jul 1, 2026

Copy link
Copy Markdown

Summary

The private-key / JWT client-credentials flow obtained tokens reactively: it sent the first /oauth2/v1/token request without a DPoP proof, let Okta reject it with invalid_dpop_proof, then retried with a proof. For an app with Require DPoP enabled, every token acquisition on this path costs an extra round trip (3 requests instead of 2) and records a failed token grant in the org System Log.

This PR attempts DPoP up front, per RFC 9449: pre-generate one DPoP key, attach a proof to the first token request, and reuse that key across the use_dpop_nonce retry. The flow then adapts to the first response:

  • Okta issues a DPoP-bound token → keep DPoP, return the key so per-request proofs are attached.
  • App does not require DPoP → Okta ignores the unsolicited proof and returns a Bearer token → drop the DPoP material and use Bearer.

getAccessTokenForDpopPrivateKey is folded into the main flow and removed. No public API change; non-DPoP apps are unaffected (still a single token request).

The change is authored in .generator/templates/client.mustache (the source template) and okta/client.go is regenerated from it — both are committed so a future regeneration stays consistent. While in the flow I also aligned the DPoP/Bearer check to the codebase's exact TokenType != "DPoP" convention (the callers already use exact matching) and corrected a comment about backoff retry behavior.

Known limitation: in JWTAuth mode (caller-supplied client_assertion, no signer) against a Require-DPoP org, the use_dpop_nonce retry replays the same assertion, which Okta may reject as a reused jti. Callers needing DPoP should use PrivateKeyAuth/JWKAuth so the SDK can mint a fresh single-use assertion. Non-DPoP orgs and the key-based modes are unaffected.

Fixes #598

Type of PR

  • Bug Fix (non-breaking fixes to existing functionality)
  • New Feature (non-breaking changes that add new functionality)
  • Documentation update
  • Test Updates
  • Other (Please describe the type)

Test Information

  • My PR required test updates

Go Version: 1.24 (module minimum; locally tested with go1.26)
Os Version: macOS (darwin/arm64)
OpenAPI Spec Version: N/A — no spec change; the fix is authored in the client.mustache template and okta/client.go is regenerated from it

Signoff

  • I have submitted a CLA for this PR
  • Each commit message explains what the commit does
  • I have updated documentation to explain what my PR does (N/A: no public API change; behavior is documented in code comments)
  • My code is covered by tests if required
  • I ran make fmt on my code
  • I did not edit any automatically generated files (fix authored in .generator/templates/client.mustache; okta/client.go is the regenerated output committed alongside)

The private-key client-credentials flow obtained tokens reactively: it sent
the first /oauth2/v1/token request without a DPoP proof, let Okta reject it
with invalid_dpop_proof, then retried with a proof. For an app with
"Require DPoP" enabled, each token acquisition that takes this path makes an
extra round trip (3 requests instead of 2) and records a failed token grant
in the org System Log.

Attempt DPoP up front instead (per RFC 9449): pre-generate one key, attach a
proof to the first token request, and reuse the key across the use_dpop_nonce
retry. Adapt to the first response — keep DPoP if Okta issues a DPoP-bound
token, fall back to Bearer if the app does not require DPoP (Okta ignores the
unsolicited proof). getAccessTokenForDpopPrivateKey is folded into the main
flow and removed. No public API change; non-DPoP apps are unaffected.
@turkdogan

Copy link
Copy Markdown
Author

I've signed and emailed the Okta Individual CLA (GitHub: turkdogan, email: turkdogan.tasdelen@backupta.com).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

1 participant