Skip to content

feat(adapters): salesforce JWT bearer grant (RFC 7523) - #61

Merged
deblasis merged 2 commits into
mainfrom
feat/salesforce-jwt-bearer
Aug 17, 2026
Merged

feat(adapters): salesforce JWT bearer grant (RFC 7523)#61
deblasis merged 2 commits into
mainfrom
feat/salesforce-jwt-bearer

Conversation

@deblasis

Copy link
Copy Markdown
Contributor

What

salesforce-style gains the JWT bearer OAuth flow — the server-to-server grant real connected apps use — closing the last item of the salesforce sweep issue (SOQL shipped in v0.22.0, SObject Collections in v0.37.0).

POST /services/oauth2/token now accepts grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer with an assertion, verified the way the real endpoint does (modulo the fixed mock certificate):

  • three dot-separated base64url-valid segments (shape guards keep the crypto builtins from 500ing on garbage)
  • JOSE header alg == RS256
  • RSA-SHA256 signature over header.payload against the adapter's fixed "connected-app certificate" (the repo's throwaway keypair material; public half in scripts/oauth.star, private half only in tests — mirroring how a real app's cert is registered out of band)
  • iss non-empty (consumer key), sub (or legacy prn) non-empty, aud ∈ {https://login.salesforce.com, https://test.salesforce.com}, exp in the future

A valid assertion mints a normal session token for the sub user. The response carries no refresh_token — a JWT-bearer client mints a fresh assertion instead of refreshing (matches the real flow). Failures return 400 {"error":"invalid_grant","error_description":"invalid assertion"}; a missing assertion is invalid_request.

Test

TestSalesforceStyleJWTBearerGrant: real RS256 assertions signed in Go —

  • valid assertion → 200, 00D access token, Bearer, no refresh_token, and the token authorizes a SOQL query (proves it's a real session)
  • forged key → 400 invalid_grant
  • properly signed but expired → 400 invalid_grant
  • wrong audience → 400 invalid_grant
  • garbage assertion → 400; missing assertion → 400 invalid_request

Gates: parse guard, QC boot, adapter lint, full go test ./..., gofmt, go vet — all green.

The token endpoint accepts grant_type=urn:ietf:params:oauth:grant-type:
jwt-bearer with an RS256 assertion, verified like the real endpoint:
3 base64url segments, alg RS256, RSA-SHA256 signature against the
adapter's fixed connected-app certificate, iss non-empty, sub (or
legacy prn) non-empty, aud a Salesforce login host, exp in the future.
A valid assertion mints a normal session for the sub user with no
refresh_token (fresh assertions replace refresh); failures return the
real invalid_grant 400. Closes the last item of the salesforce sweep
issue (SOQL v0.22.0, SObject Collections v0.37.0).
Review findings on PR #61, all fixed:

- iss/sub/prn claims must be actual strings: JSON null decodes to
  Starlark None and None == "" is False, so a signed {"sub":null}
  assertion minted a session with a null username past the emptiness
  guards. _claim_str (mirrors _claim_int) now gates iss, sub, prn and
  the username extraction. Same latent hole fixed in google-iam's iss
  check, with a pinned null-iss negative.
- exp must be within ~5 minutes of now (plus the documented clock-skew
  allowance) — the real endpoint rejects long-lived assertions, so a
  client minting them should fail against stunt too.
- tests now pin: prn legacy fallback, sandbox aud, alg!=RS256, null
  sub/iss, float exp, exp==now boundary, 1h exp rejection.
@deblasis
deblasis merged commit 8506fc6 into main Aug 17, 2026
1 check passed
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.

1 participant