2

We are setting up a Salesforce Community and our community members should be able to automatically login using the credentials from an extenal platform when clicking on a link from this platform. I'm trying to generate a JWT token (Please find below the class I've made to generate the JWT) and I've created a connected app to get a consumer key.

public inherited sharing class JWTAuthFlow {

public static string createJWT(String iss, String sub, String aud, String endpoint){ // Static Resource to get the Key that will be used for Crypto-Sign StaticResource jwtPrivate = [SELECT Id, Body FROM StaticResource WHERE Name = 'JWT_Private_key' LIMIT 1]; String privateKey = jwtPrivate.Body.toString();

//Create Body of the Json that contains enpoint URL, Consumer Key, Type, ect
JSONGenerator jsonHeader = JSON.createGenerator(false);
jsonHeader.writeStartObject();
jsonHeader.writeStringField('alg', 'RS256');
jsonHeader.writeStringField('typ', 'JWT');
jsonHeader.writeEndObject();
String encodedHeader = EncodingUtil.base64Encode(Blob.valueOf(jsonHeader.getAsString()));

JSONGenerator jsonBody = JSON.createGenerator(false);
jsonBody.writeStartObject();
jsonBody.writeStringField('iss', iss);
jsonBody.writeStringField('sub', sub);
jsonBody.writeStringField('aud', aud);
jsonBody.writeNumberField('exp', Datetime.now().addMinutes(3).getTime());
jsonBody.writeEndObject();
String encodedBody = EncodingUtil.base64Encode(Blob.valueOf(jsonBody.getAsString()));
String jwtRequest = encodedHeader + '.' + encodedBody;

// Sign JWT Request
Blob key = EncodingUtil.base64Decode(privateKey);
Blob rs256sig = Crypto.sign('RSA-SHA256', Blob.valueOf(jwtRequest), key);
String signature = EncodingUtil.base64Encode(rs256sig);
signature = signature.replace('+', '-');
signature = signature.replace('/', '_');
String signedJwtRequest = jwtRequest + '.' + signature;

// Create the payload
String payload = 'grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer';
payload += '&assertion=' + signedJwtRequest;

// Create a HttpRequest and send it to server
Http httpObj = new Http();
HttpRequest req = new HttpRequest();
HttpResponse res;
req.setEndpoint(endpoint);
req.setMethod('POST');
req.setHeader('Content-Type', 'application/x-www-form-urlencoded');
req.setBody(payload);
res = httpObj.send(req);

if(res.getStatusCode() == 200){
    Map<String, Object> mapTokenResponse = (Map<String, Object>)JSON.deserializeUntyped(res.getBody());
    return (String)mapTokenResponse.get('access_token');
}

return res.getBody();

}

}

I have the following error when I try to generate a JWT: 'Unrecognized base64 character:-' (I can see from system.debug that it generates the header and the body, but not the signature).

I don't know what I'm doing wrong ?

It's the first time I'm working with JWT so I don't know if I've missed something. The developer from the external app is waiting for the token, and the returnURL (the path). Is there something else I need to give him ?

identigral
  • 7,543
  • 29
  • 32
  • 42
chloe
  • 243
  • 2
  • 15

1 Answers1

3

A Named Credential can issue a JWT out of the box (see this answer for more details on how it works)

HttpRequest req = new HttpRequest();
req.setEndpoint('callout:MyJWT/some_path');
req.setMethod('POST');
Http http = new Http();
HTTPResponse res = http.send(req);
System.debug(res.getBody());

If you need more flexibility with payload, use Auth.JWT class:

Auth.JWT jwt = new Auth.JWT();
// add standard claims
jwt.setIssuer('https://blah.my.salesforce.com');
...
// add custom claims
Map<String,String> claims = new Map<String,String>();
claims.put('foo','bar');
jwt.setAdditionalClaims(claims);

// Generate token Auth.JWS signedToken = new Auth.JWS(jwt, 'MyKeyPairName'); String serializedToken = signedToken.getCompactSerialization(); System.debug(serializedToken);

identigral
  • 7,543
  • 29
  • 32
  • 42
  • 1
    For what it's worth, Auth.JWS only works if the keypair is in Salesforce's certificate and key management. – Derek F Oct 23 '20 at 17:04
  • Thanks identigral ! So if I understand well, since the external platform also uses JWT, I'll chose the JWT Token option then ? If I need to add additional claims, I won't be able to use Named Credential though right ? I'll only have to use Auth.JWT ? And I didn't have to create a Connected App ? – chloe Oct 23 '20 at 17:16
  • @chloe Yes, you got it. Connected app is for calling into Salesforce, you don't need it for calling out from Salesforce to an external non-Salesforce service. – identigral Oct 23 '20 at 18:40
  • Named credential can issue JWT OOTB as long you can import certificate (Java KeyStore format) into Salesforce otherwise you cannot use this method – compski May 03 '23 at 03:08