0

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 ?

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);
| improve this answer | |
  • For what it's worth, Auth.JWS only works if the keypair is in Salesforce's certificate and key management. – Derek F Oct 23 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 ? – tiph Oct 23 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 at 18:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.