Machine Identity15 min read

client_secret vs JWT Client Assertion vs mTLS: A Buyer's Guide to OAuth 2.0 Client Authentication Methods

OAuth 2.0 supports several methods for authenticating a client to the authorization server. This guide compares client_secret_basic and client_secret_post (the original shared-secret methods) against private_key_jwt (RFC 7523 JWT client assertion) and tls_client_auth (RFC 8705 mTLS), with practical guidance on when each is appropriate and why production deployments are converging on the cryptographic methods.

Download PDFBy ScrambleID Team·Updated June 11, 2026

Download PDF: client_secret vs JWT vs mTLS

In one sentence: OAuth 2.0 client authentication has evolved from shared-secret methods (client_secret_basic, client_secret_post) that are now widely deprecated for high-trust integrations to cryptographic methods (private_key_jwt, tls_client_auth, self_signed_tls_client_auth) that eliminate the structural leakage problem; production deployments should default to the cryptographic methods, with the choice between JWT assertion and mTLS driven by operational fit.

TL;DR (canonical)

  • Shared-secret methods (client_secret_basic, client_secret_post) were appropriate for low-stakes integrations in 2012 when OAuth 2.0 was published. They are no longer appropriate for any high-trust production integration.
  • private_key_jwt (RFC 7523) is the cryptographic equivalent of client_secret_basic: the client signs a JWT assertion with its private key. The private key never leaves the client. No shared secret to leak.
  • tls_client_auth and self_signed_tls_client_auth (RFC 8705) authenticate the client via TLS client certificate at the TLS handshake layer. Both produce sender-constrained access tokens.
  • DPoP (RFC 9449) is the application-layer alternative to mTLS for sender-constraining access tokens at the resource server.
  • The current best-practice production stack: private_key_jwt at the token endpoint plus mTLS or DPoP at the resource server.
  • Standards momentum: RFC 9700 (OAuth 2.0 Security BCP) recommends moving away from the shared-secret methods for high-trust integrations; FAPI 2.0, UK Open Banking, PSD2 RTS, and most regulator-driven profiles require cryptographic methods.

The five methods, in one table

MethodRFCAuthenticates howSender-constrains tokensProduction posture
client_secret_basicRFC 6749HTTP Basic auth header with shared secretNoDeprecated for high-trust
client_secret_postRFC 6749Form parameter with shared secretNoDeprecated for high-trust
client_secret_jwtOIDC Core (method name) / RFC 7523 (assertion profile)HMAC-signed JWT with shared secretNoNiche (legacy systems)
private_key_jwtOIDC Core (method name) / RFC 7523 (assertion profile)Asymmetric-signed JWT with private keyNo (use with PoP at resource)Recommended
tls_client_authRFC 8705TLS client certificate from CAYes (cnf.x5t#S256)Recommended
self_signed_tls_client_authRFC 8705Self-signed TLS client certificateYes (cnf.x5t#S256)Recommended

The shift is generational. Methods designed in the OAuth 2.0 era (2012) prioritized simplicity. Methods designed in the OAuth 2.0 Security BCP era (2025) prioritize sender-constraint and cryptographic credentials.

The shared-secret methods (deprecated for high-trust)

client_secret_basic

The original OAuth 2.0 client authentication method. The client includes its client_id and client_secret in an HTTP Basic auth header to the token endpoint:

POST /oauth2/token
Authorization: Basic <base64(client_id:client_secret)>
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&scope=api:read

The authorization server validates the secret against its registered value and issues an access token.

Why it was appropriate in 2012: simple to implement, OAuth 2.0 was new, integrations were lower-stakes, the alternatives (X.509 client certs, signed JWTs) were operationally heavy.

Why it isn't appropriate in 2026: the shared secret accumulates in repos, vaults, environment files, and developer laptops. It leaks. Once leaked, it's valid until rotated and can be replayed from anywhere. The structural failure mode is documented; leaked OAuth client secrets have driven multiple breach disclosures.

client_secret_post

Identical to client_secret_basic except the credentials are submitted as form parameters in the request body rather than the Authorization header. Same structural failure mode.

client_secret_jwt

The client signs a JWT with the shared secret using HMAC. Slightly better than client_secret_basic because the JWT carries an audience claim (preventing replay against a different token endpoint) and an exp claim (limiting replay window). Still uses a shared secret. Niche today; mostly seen in legacy systems.

private_key_jwt: the cryptographic equivalent

private_key_jwt (the method name comes from OIDC Core; the underlying assertion profile is RFC 7523) is the cryptographic alternative to client_secret_basic. Instead of a shared secret, the client has an asymmetric key pair. The private key is held in hardware-backed storage on the client; the public key is registered with the authorization server.

To authenticate, the client signs a JWT assertion with its private key:

{
  "iss": "<client_id>",
  "sub": "<client_id>",
  "aud": "https://id.scrambleid.com/oauth2/token",
  "exp": 1715040300,
  "iat": 1715040240,
  "jti": "<unique-id>"
}

The JWT is submitted as a client_assertion:

POST /oauth2/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
client_assertion=eyJhbGc...
scope=api:read

The authorization server verifies the JWT signature against the client's registered public key. If valid, it issues an access token.

What private_key_jwt gets you

No shared secret. The private key never leaves the client. There is nothing to leak via shared-secret pathways.

Replay prevention. The jti claim is enforced as single-use within the TTL window. The exp claim limits the JWT validity. The aud claim binds the JWT to the specific token endpoint.

Audience binding. A JWT signed for aud=https://id.scrambleid.com/oauth2/token cannot be replayed against a different authorization server.

Standard signing algorithms. ES256 and RS256 are the typical choices. ES256 (ECDSA P-256 with SHA-256) is the modern default; RS256 (RSA with SHA-256) is more widely supported in older libraries.

What private_key_jwt does not get you

Sender-constrained access tokens. private_key_jwt authenticates the client to the authorization server but does not by itself sender-constrain the issued access token. A token issued via private_key_jwt can be replayed if stolen. To sender-constrain, layer mTLS or DPoP at the resource server.

Mutual-TLS guarantee on the token endpoint. The token endpoint connection itself is one-way TLS (server-authenticated). The client authenticates via the JWT, not via TLS. This is fine for most threat models; if you need mutual TLS at the connection level, use tls_client_auth.

tls_client_auth and self_signed_tls_client_auth

RFC 8705 defines mTLS-based client authentication for OAuth 2.0. Two profiles:

tls_client_auth (PKI-based)

The client presents a TLS client certificate issued by a CA recognized by the authorization server. The TLS handshake completes with mutual authentication; the authorization server identifies the client by the certificate's subject DN, SAN, or other field as configured.

When to use: PKI infrastructure exists and is managed (enterprise CA, cloud-managed PKI). Certificates can be issued, rotated, and revoked through standard CA operations.

self_signed_tls_client_auth

The client presents a self-signed TLS certificate that has been pre-registered with the authorization server. The authorization server doesn't validate against a CA; it validates the certificate's thumbprint against the registered value.

When to use: PKI infrastructure is heavy or unavailable. Each client registers its own certificate. Certificate rotation is per-client (the client generates a new certificate and registers it).

Sender-constrained tokens via mTLS

The key benefit of both mTLS profiles: the access token issued can be bound to the client's TLS certificate. The token's cnf claim includes x5t#S256 (the SHA-256 thumbprint of the certificate). The resource server validates the incoming TLS client certificate against the thumbprint at every request:

{
  "access_token": "eyJ...",
  "token_type": "Bearer",
  "expires_in": 300,
  "scope": "api:read",
  "cnf": {
    "x5t#S256": "<thumbprint>"
  }
}

A stolen token without the matching client certificate is rejected with invalid_token. This is the property that makes mTLS specifically valuable for sender-constraint.

DPoP: the alternative to mTLS for sender-constraint

RFC 9449 defines DPoP (Demonstrating Proof of Possession) as an application-layer alternative to mTLS for sender-constraining access tokens.

The flow:

  1. Client authenticates to the authorization server (typically via private_key_jwt) and includes a DPoP proof header containing a JWT signed with the client's DPoP key.
  2. The authorization server issues an access token with cnf.jkt set to the SHA-256 thumbprint of the DPoP public key.
  3. For each request to the resource server, the client includes a DPoP proof header signed with the DPoP private key, plus the access token.
  4. The resource server validates: the access token's cnf.jkt matches the DPoP proof's public key thumbprint, the DPoP proof is fresh (single-use jti, recent iat), and the proof is bound to the specific request method and URL.

When to use: mTLS is operationally impractical (load balancers don't pass client certs, proxies terminate TLS, browsers don't support arbitrary TLS configs). DPoP gives the same end-to-end sender-constraint at the application layer.

When mTLS is preferred over DPoP: transport-layer integrity matters (regulatory environments where TLS-layer authentication is required), the infrastructure supports mTLS cleanly, certificate management is already in place.

For deeper coverage of mTLS and DPoP, see Sender-Constrained Tokens (mTLS, DPoP).

Decision matrix: which method when

ScenarioRecommended client authRecommended PoP at resource
Internal service-to-service, controlled environmentprivate_key_jwtmTLS
Internal service-to-service, mixed cloudprivate_key_jwtDPoP
Cross-org partner integration, regulator-aligned (banking, healthcare)private_key_jwt or tls_client_authmTLS
Cross-org partner integration, web-friendlyprivate_key_jwtDPoP
AI agent calling toolsprivate_key_jwtmTLS or DPoP
MCP server authenticationprivate_key_jwtmTLS or DPoP
CI/CD pipeline (GitHub Actions OIDC federated to cloud)OIDC federation(cloud-native short-lived credential)
Legacy system that doesn't support cryptographic authclient_secret_basic (with explicit residual-risk decision and migration plan)None (accept)

The default for new integrations is private_key_jwt plus DPoP. Move to mTLS where infrastructure supports it cleanly. Use client_secret_basic only as a documented exception with a migration plan.

What to ask vendors and partners

When evaluating an OAuth 2.0 integration with a vendor, partner, or service:

  1. Which client authentication methods do you support? Look for private_key_jwt, tls_client_auth, self_signed_tls_client_auth at minimum. If only client_secret_basic is supported, the vendor's roadmap matters.
  2. Do you support sender-constrained tokens? mTLS via RFC 8705, or DPoP via RFC 9449. If neither, stolen tokens are unbounded.
  3. What's the key rotation story? private_key_jwt requires JWKS publication and rotation; mTLS requires certificate management. Mature vendors have automation.
  4. What's the JWKS distribution path? A standards-compliant vendor publishes a JWKS endpoint (/.well-known/jwks.json) that resource servers can cache.
  5. What scope of access tokens do you grant? Coarse-grained "api:full" scopes are concerning; fine-grained per-tool or per-resource scopes are mature.
  6. What's your TTL policy? Short access-token TTLs (≤ 5-15 minutes) are mature; long TTLs (hours) are concerning; refresh tokens for non-human clients are concerning.
  7. What's the audit and revocation story? Audit per-issuance, immediate revocation via API.
  8. Do you support RFC 8693 Token Exchange? For multi-hop scenarios (especially AI agent chains), Token Exchange is the standards-based pattern.

Migration guidance

For an existing OAuth 2.0 integration on client_secret_basic:

Phase 1: confirm vendor support. Verify the partner authorization server supports private_key_jwt or mTLS. If not, escalate the vendor relationship for support.

Phase 2: generate and register key material. Generate an asymmetric key pair (ES256 recommended). Store the private key in hardware-backed storage. Register the public key with the partner.

Phase 3: implement parallel auth. Most authorization servers allow a client to be configured with multiple auth methods simultaneously. Configure both client_secret_basic and private_key_jwt. Verify both work.

Phase 4: migrate the workload. Switch the workload to use private_key_jwt. Validate functionality.

Phase 5: revoke the shared secret. Once the workload is verified on the new method, revoke the shared secret. The migration is complete.

Phase 6: layer sender-constraint at the resource. If the vendor's resource server supports mTLS or DPoP for sender-constrained tokens, enable it. This is often a separate effort from the client-auth migration.

Anti-patterns

  1. Storing private keys in vaults indefinitely. The vault is an exception store. Hardware-backed storage (TPM, HSM, keystore, cloud KMS) is the right home for client private keys.
  2. Using the same key for client_secret_jwt and private_key_jwt. They are different methods; the asymmetric key for private_key_jwt should never be a shared secret.
  3. Long-lived JWT assertions. The JWT assertion (exp claim) should be short, typically ≤ 60 seconds. Don't pre-mint assertions for hours of validity.
  4. Skipping audience validation. The aud claim binds the assertion to the specific token endpoint. Skipping this validation allows replay against a different AS.
  5. Reusing jti values. Single-use replay prevention requires jti enforcement at the AS. Mature ASs do this; verify if uncertain.
  6. No JWKS rotation. The public key set should rotate on a cadence, with a grace period that supports zero-downtime rotation (dual-kid overlap).
  7. Bearer-token transmission over HTTP. Even with private_key_jwt at the token endpoint, the access token transmission to the resource server must be over TLS (or sender-constrained via mTLS/DPoP).

Standards alignment

StandardRelevance
RFC 6749OAuth 2.0 Authorization Framework (defines client_secret_basic, client_secret_post)
RFC 7521Assertion framework for OAuth 2.0
RFC 7523JWT Profile for OAuth 2.0 (the assertion profile behind client_secret_jwt and private_key_jwt; the method names come from OIDC Core Section 9)
RFC 8705OAuth 2.0 Mutual-TLS (defines tls_client_auth, self_signed_tls_client_auth, mTLS-bound tokens)
RFC 9449DPoP (application-layer PoP)
RFC 9700OAuth 2.0 Security BCP (recommends moving away from shared-secret methods for high-trust)
OpenID Connect CoreAligns client authentication methods with OAuth 2.0
FAPI 2.0Financial-grade API profile, requires cryptographic client auth
UK Open BankingRequires private_key_jwt or tls_client_auth
NIST SP 800-207Zero Trust applied to client authentication

Key Takeaway

OAuth 2.0 client authentication has five primary methods: client_secret_basic and client_secret_post (shared-secret, original OAuth 2.0; RFC 9700 recommends moving away from them for high-trust); client_secret_jwt (HMAC-signed JWT with shared secret, niche); private_key_jwt (RFC 7523 asymmetric-signed JWT with hardware-bound private key, the cryptographic equivalent of client_secret_basic); tls_client_auth and self_signed_tls_client_auth (RFC 8705 TLS client certificate, produces sender-constrained access tokens via cnf.x5t#S256). The current best-practice production stack is private_key_jwt at the token endpoint plus mTLS or DPoP at the resource server for sender-constrained access tokens. The shift away from shared-secret methods is driven by the structural leakage problem: shared secrets accumulate, leak, and once leaked are valid until rotated. Cryptographic methods eliminate the shared secret entirely. FAPI 2.0, UK Open Banking, PSD2 RTS, and most regulator-driven profiles require the cryptographic methods. New integrations should default to private_key_jwt plus DPoP, moving to mTLS where infrastructure supports it cleanly.

FAQ

What are the OAuth 2.0 client authentication methods?

OAuth 2.0 (RFC 6749) and its extensions define several methods. The original methods use a shared secret: client_secret_basic (HTTP Basic auth) and client_secret_post (form parameter). The cryptographic methods use a private key: private_key_jwt (RFC 7523, the client signs a JWT assertion with its private key), tls_client_auth (RFC 8705, the client presents a CA-issued TLS certificate), and self_signed_tls_client_auth (RFC 8705, self-signed TLS certificate). Modern deployments are increasingly required to use the cryptographic methods; OAuth 2.0 Security BCP (RFC 9700) recommends moving away from shared secrets.

Why are client_secret_basic and client_secret_post becoming deprecated?

Shared secrets have a structural failure mode: they leak. They get committed to repos, exposed in logs, harvested from developer laptops, exfiltrated from compromised vaults. Once leaked, they are valid until manually rotated and can be replayed from anywhere. RFC 9700 (OAuth 2.0 Security Best Current Practice) recommends moving away from client_secret_basic for high-trust integrations. Many regulator-driven specifications (UK Open Banking, FAPI 2.0, PSD2 RTS) require private_key_jwt or mTLS for sender-constrained tokens.

What is private_key_jwt?

private_key_jwt is the OAuth 2.0 client authentication method defined in RFC 7523 where the client authenticates by signing a JWT assertion with its private key and submitting the JWT to the authorization server's token endpoint. The authorization server verifies the signature against the client's registered public key. The private key never leaves the client's secure environment. There is no shared secret, no risk of leakage, and the credential is bound to the specific token endpoint via the audience claim. private_key_jwt is the cryptographic equivalent of client_secret_basic.

What is tls_client_auth?

tls_client_auth is the OAuth 2.0 client authentication method defined in RFC 8705 where the client authenticates via a TLS client certificate during the TLS handshake to the authorization server. RFC 8705 defines two profiles: tls_client_auth uses certificates from a recognized CA; self_signed_tls_client_auth uses self-signed certificates pre-registered with the authorization server. Both produce sender-constrained access tokens via the cnf claim with x5t#S256 thumbprint. mTLS is appropriate where TLS-layer certificate authentication is operationally feasible (controlled network, infrastructure that supports mTLS termination).

When should I use private_key_jwt vs mTLS?

private_key_jwt is the easier path when mTLS is operationally complex: load balancers that don't pass client certs through, cloud proxies that terminate TLS, language ecosystems where TLS client cert configuration is awkward. mTLS is the right choice when sender-constrained access tokens are required (the resource server validates the access token against the same TLS certificate that authenticated the client) and the infrastructure supports it. Many production deployments use private_key_jwt at the token endpoint and mTLS at the resource server, getting both standardized client auth and sender-constrained tokens.

Do I need DPoP if I'm using mTLS?

DPoP (RFC 9449) is the alternative to mTLS for sender-constraining access tokens at the resource layer. DPoP works at the application layer: the client signs each request with a key whose thumbprint is in the token's cnf.jkt claim. Use DPoP where mTLS is impractical (proxies that don't pass client certs, browsers that don't support arbitrary TLS configurations, environments where managing certificates per workload is operationally heavy). private_key_jwt at the token endpoint plus DPoP at the resource gives the same end-to-end sender-constraint property as mTLS without the TLS-layer complexity.

References (public)


Ready to eliminate shared secrets?

See how ScrambleID provides cryptographic proof across every channel.

See Our Solutions