I have a connected app that I would like to connect to using a JWT flow using a bearer token.

Using anonymous Apex I have:

// The consumer key for a connected app of mine
String iss = '<MY CONSUMER KEY>';
String aud = 'https://test.salesforce.com';
String sub = 'my_user_name';
Long exp = DateTime.now().addMinutes(5).getTime();

Auth.JWT jwt = new Auth.JWT();

// Storing the certificate in Salesforce is a requirement for using the JWS class
// I just created a certificate under 'Certificate and Key Management' and using that below (jini)

Auth.JWS jws = new Auth.JWS(jwt, 'jini');

String tokenEndpoint = 'https://my.sandbox.my.salesforce.com/services/oauth2/token';
Auth.JWTBearerTokenExchange bearer = new Auth.JWTBearerTokenExchange(tokenEndpoint, jws);
String accessToken = bearer.getAccessToken();

When I execute it, I get:

18:29:26:041 EXCEPTION_THROWN [33]|Auth.JWTBearerTokenExchange.JWTBearerTokenExchangeException: Error 400: {"error":"invalid_grant","error_description":"expired authorization code"}

My user belongs to one of the pre-authorized profiles for the connected app. I have also added my sandbox url to the remote site settings although that may not be needed.

What am I doing wrong? I think it may be due to the certificate but I am not sure.

1 Answer 1


You're creating a token that expires in 5 seconds from 'now' and by the time it arrives at the destination, it has expired, thus the error message.

When you call setValidityLength(...), you're setting the value of the exp (expiration) claim. The format of expiration claim is number of seconds elapsed since epoch. From RFC 7519

4.1.4. "exp" (Expiration Time) Claim

The "exp" (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing. The processing of the "exp" claim requires that the current date/time MUST be before the expiration date/time listed in the "exp" claim. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL.


A JSON numeric value representing the number of seconds from 1970-01-01T00:00:00Z UTC until the specified UTC date/time, ignoring leap seconds. This is equivalent to the IEEE Std 1003.1, 2013 Edition [POSIX.1] definition "Seconds Since the Epoch"

setValidityLength(...) wants a duration (interval) in seconds. Auth.JWT will then translate the duration to the number of seconds since epoch by adding the duration to the current date/time.

This works:

jwt.setValidityLength(5*60); // expire 5 minutes from now

Alternatively, you can skip a call to setValidityLength(...) and you'll get a default expiration interval...which happens to be 5 minutes.

Last but not least, if what you're trying to accomplish is a callout from one SF org to another, you don't need JWT. You can use an out of the box Salesforce auth provider and grab the token from that.

  • Thanks a lot for your response. I did that and now I get Line: 52, Column: 1 Auth.JWTBearerTokenExchange.JWTBearerTokenExchangeException: Error 400: {"error":"invalid_grant","error_description":"invalid assertion"} which i guess is a different problem to chase.
    – kratos
    Oct 25, 2019 at 16:02
  • @kratos Yeah that is likely a cert-related issue.
    – identigral
    Oct 25, 2019 at 16:48

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .