OAuth 2.0 for First-Party Applications: Generate a Client Attestation JWT
When using headless identity flows that implement the OAuth 2.0 for First-Party Applications draft standard, use a client attestation JWT to prove that apps sending requests to Salesforce are first-party apps created by your company.
Required Editions
| Available in: both Salesforce Classic and Lightning Experience |
| Available in: Enterprise, Unlimited, and Developer Editions |
Salesforce requires that a JWT is signed using RSA SHA256, which uses an uploaded certificate as the signing secret. Before you get started, complete these prerequisites.
- Upload an X509 Certificate to a Java Key Store (JKS). The certificate size can't exceed 4 KB. If it does, try using a DER-encoded file to reduce the size.
- Register the certificate with your external client app using the
clientAssertionCertificatefield in the ExtlClntAppOauthSettings type. - Build an app that generates a JWT, which is signed with the X509 Certificate’s private key.
The associated external client app uses the certificate to verify the signature. The JWT must
conform with the general format rules specified in https://tools.ietf.org/html/rfc7519.
Note Salesforce doesn’t require JWT ID (JTI) claims in your JWT bearer tokens. However, if you pass a JTI claim in your JWT bearer token, Salesforce validates that the JTI claim hasn’t been sent before. This validation prevents JWT replay attacks.
To create a valid JWT, take these steps.
- Construct a JWT header with this format:
{"alg":"RS256"}. - Base64-URL encode the JWT header as defined in http://tools.ietf.org/html/rfc4648#page-7.
The result is similar to
eyJhbGciOiJSUzI1NiJ9. - Construct a JSON Claims Set for the JWT with these parameters.
Parameter Description issThe issuer must contain the OAuth client_idfor the external client app for which you registered the certificate.audThe audience identifies the authorization server as an intended audience. The authorization server must verify that it’s an intended audience for the token.
Use your Experience Cloud site URL, such as https://MyExperienceCloudSite.my.site.com.
expThe date and time at which the token expires, expressed as the number of seconds from 1970-01-01T0:0:0Z measured in UTC. Salesforce allows a 3-minute buffer for clock skew. For example, if the expiration time is set to 1735743600 seconds or January 1, 2025 at 15:00:00 UTC, the token is still valid until 15:03:00 UTC on this date. Here's an example JWT claims set.{"iss": "3MVG99OxTyEMCQ3gNp2PjkqeZKxnmAiG1xV4oHh9AKL_rSK.BoSVPGZHQ ukXnVjzRgSuQqGn75NL7yfkQcyy7", "aud": "https://MyExperienceCloudSite.my.site.com", "exp": "1333685628"} - Base64url encode the JWT Claims Set without any line breaks. Here's an example.
eyJpc3MiOiAiM01WRzk5T3hUeUVNQ1EzZ05wMlBqa3FlWkt4bm1BaUcxeFY0b0hoOUFLTF9yU0su Qm9TVlBHWkhRdWtYblZqelJnU3VRcUduNzVOTDd5ZmtRY3l5NyIsICJwcm4iOiAibXlAZW1haWwu Y29tIiwgImF1ZCI6ICJodHRwczovL2xvZ2luLnNhbGVzZm9yY2UuY29tIiwgImV4cCI6ICIxMzMz Njg1NjI4In0= - Create a string for the encoded JWT Header and the encoded JWT Claims Set in this
format.
encoded_JWT_Header + "." + encoded_JWT_Claims_SetIn this example, the encoded JWT Header is highlighted.eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiAiM01WRzk5T3hUeUVNQ1EzZ05wMlBqa3FlWkt4bm1BaUcxeFY0b0hoOUFLTF9yU0su Qm9TVlBHWkhRdWtYblZqelJnU3VRcUduNzVOTDd5ZmtRY3l5NyIsICJwcm4iOiAibXlAZW1haWwu Y29tIiwgImF1ZCI6ICJodHRwczovL2xvZ2luLnNhbGVzZm9yY2UuY29tIiwgImV4cCI6ICIxMzMz Njg1NjI4In0= - Download the X509 Certificate from the JKS.
- Sign the resulting string using RSA SHA256.
- Create a string of the string from step 5. Create the string in this format.
existing_string + "." + base64_encoded_signatureIn this example, the base64 encoded signature is highlighted.eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiAiM01WRzk5T3hUeUVNQ1EzZ05wMlBqa3FlWkt4bm1BaUcxeFY0b0hoOUFLTF9yU0su Qm9TVlBHWkhRdWtYblZqelJnU3VRcUduNzVOTDd5ZmtRY3l5NyIsICJwcm4iOiAibXlAZW1haWwu Y29tIiwgImF1ZCI6ICJodHRwczovL2xvZ2luLnNhbGVzZm9yY2UuY29tIiwgImV4cCI6ICIxMzMz Njg1NjI4In0=.iYCthqWCQucwi35yFs-nWNgpF5NA_a46fXDTNIY8ACko6BaEtQ9E6h4Hn1l_pcwcK I_GlmfUO2dJDg1A610t09TeoPagJsZDm_H83bsoZUoI8LpAA1s-2aj_Wbysqb1j4uDToz 480WtEbkwIv09sIeS_-QuWak2RXOl1Krnf72mpVGS4WWSULodgNzlKHHyjAMAHiBHIDNt 36y2L2Bh7M8TNWiKa_BNM6s1FNKDAwHEWQrNtAeReXgRy0MZgQY2rZtqT2FcDyjY3JVQb En_CSjH2WV7ZlUwsKHqGfI7hzeEvVdfOjH9NuaJozxvhPF489IgW6cntPuT2V647JWi7ngUse the signed JWT in theclient_assertionparameter when you configure headless flows using the OAuth 2.0 for First-Party Applications standard.
Did this article solve your issue?
Let us know so we can improve!

