• Michael Johnson 149
  • NEWBIE
  • 10 Points
  • Member since 2022

  • Chatter
    Feed
  • 0
    Best Answers
  • 0
    Likes Received
  • 0
    Likes Given
  • 2
    Questions
  • 3
    Replies

I have been trying to replicate what jwt.encode(object payload, string token, string algorithm) does in Apex, but have fallen short. Here is my very simple Python script I am trying to replicate, and my Apex class I've put together after a ton of digging and research but am still not getting expected results.

Python script

import jwt

service_account_id = 'abc123'
jti_code = 'aaa-bbb-ccc'
private_key_str = '-----BEGIN RSA PRIVATE KEY-----\nMIIBOgIBAAJBAKj34GkxFhD90vcNLYLInFEX6Ppy1tPf9Cnzj4p4WGeKLs1Pt8Qu\nKUpRKfFLfRYC9AIKjbJTWit+CqvjWYzvQwECAwEAAQJAIJLixBy2qpFoS4DSmoEm\no3qGy0t6z09AIJtH+5OeRV1be+N4cDYJKffGzDa88vQENZiRm0GRq6a+HPGQMd2k\nTQIhAKMSvzIBnni7ot/OSie2TmJLY4SwTQAevXysE2RbFDYdAiEBCUEaRQnMnbp7\n9mxDXDf6AU0cN/RPBjb9qSHDcWZHGzUCIG2Es59z8ugGrDY+pxLQnwfotadxd+Uy\nv/Ow5T0q5gIJAiEAyS4RaI9YG8EWx/2w0T67ZUVAw8eOMB6BIUg0Xcu+3okCIBOs\n/5OiPgoTdSy7bcF9IGpSE8ZgGKzgYQVZeN97YE00\n-----END RSA PRIVATE KEY-----'

payload = {
    "iss": service_account_id,
    "aud": "https://test.com",
    "sub": service_account_id,
    "iat": 12345678,
    "jti": jti_code
}

jwt_token = jwt.encode(payload, private_key_str, algorithm="RS256")

jwt_token_str = str(jwt_token, 'UTF-8')
print(jwt_token_str)
 



Output : eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJhYmMxMjMiLCJhdWQiOiJodHRwczovL3Rlc3QuY29tIiwic3ViIjoiYWJjMTIzIiwiaWF0IjoxMjM0NTY3OCwianRpIjoiYWFhLWJiYi1jY2MifQ.VX4eVDymRgXkIMq0mJT2mNEuBstvePKQsSn8_ZJ2wIWh2kF2IGbvayEl6NYA3y4X-1csX1gBAvekYvU0mNpMkw

Apex Class
 

public class SSOJWTGenerator {

    public static String generateJWT() {
        String jti_code = 'aaa-bbb-ccc';
        String service_account_id = 'abc123';
        String privateKey = '-----BEGIN RSA PRIVATE KEY-----\nMIIBOgIBAAJBAKj34GkxFhD90vcNLYLInFEX6Ppy1tPf9Cnzj4p4WGeKLs1Pt8Qu\nKUpRKfFLfRYC9AIKjbJTWit+CqvjWYzvQwECAwEAAQJAIJLixBy2qpFoS4DSmoEm\no3qGy0t6z09AIJtH+5OeRV1be+N4cDYJKffGzDa88vQENZiRm0GRq6a+HPGQMd2k\nTQIhAKMSvzIBnni7ot/OSie2TmJLY4SwTQAevXysE2RbFDYdAiEBCUEaRQnMnbp7\n9mxDXDf6AU0cN/RPBjb9qSHDcWZHGzUCIG2Es59z8ugGrDY+pxLQnwfotadxd+Uy\nv/Ow5T0q5gIJAiEAyS4RaI9YG8EWx/2w0T67ZUVAw8eOMB6BIUg0Xcu+3okCIBOs\n/5OiPgoTdSy7bcF9IGpSE8ZgGKzgYQVZeN97YE00\n-----END RSA PRIVATE KEY-----';

        String alg = 'RS256';
        String typ = 'JWT';

        Map<String, Object> header = new Map<String, Object>{
                'alg' => alg,
                'typ' => typ
        };
        String encodedHeader = base64UrlEncode(blob.valueOf(JSON.serialize(header)));

        Map<String, Object> claimSet = new Map<String, Object>{
                'iss' => service_account_id,
                'aud' => 'https://test.com',
                'sub' => service_account_id,
                'iat' => 12345678,
                'jti' => jti_code
        };

        String encodedClaimSet = base64UrlEncode(Blob.valueOf(JSON.serialize(claimSet)));

        privateKey = privateKey.replace('-----BEGIN RSA PRIVATE KEY-----', '');
        privateKey = privateKey.replace('-----END RSA PRIVATE KEY-----', '');
        Blob privateKeyBlob = EncodingUtil.base64Decode(privateKey);

        Blob signatureBlob = Crypto.sign('RSA-SHA256', Blob.valueOf(encodedHeader+'.'+encodedClaimSet), privateKeyBlob);
        String signature = base64UrlEncode(signatureBlob);

        String jwtToken = encodedHeader + '.' + encodedClaimSet + '.' + signature;

        return EncodingUtil.urlEncode(jwtToken,'UTF-8');

    }

    private static String base64UrlEncode(String input) {
        // Replace + with -, / with _, and remove any trailing = signs
        String base64 = EncodingUtil.base64Encode(Blob.valueOf(input));
        base64 = base64.replace('+', '-').replace('/', '_').replaceAll('\\=+$', '');
        return base64;
    }

    private static String base64UrlEncode(Blob input) {
        // Replace + with -, / with _, and remove any trailing = signs
        String base64 = EncodingUtil.base64Encode(input);
        base64 = base64.replace('+', '-').replace('/', '_').replaceAll('\\=+$', '');
        return base64;
    }

}

Output : 

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJqdGkiOiJhYWEtYmJiLWNjYyIsImlhdCI6MTIzNDU2NzgsInN1YiI6ImFiYzEyMyIsImF1ZCI6Imh0dHBzOi8vdGVzdC5jb20iLCJpc3MiOiJhYmMxMjMifQ.Lj38x5PfUrIyDEE_8xA51pAJ8hzDt2QMv6GHNAmDEfJyx9R4gyRCsCXSaxCX7tQE-7ssoILc7WfUq4JoXWRELw


I am getting close to the expected output <base64-encoded header>.<base64-encoded claims>.<base64-encoded signature>.

The header is generating correctly base64 encoded, the claims are generating correctly base64 encoded, but the signatures differ from what is generated in the jwt.encode() Python method to the Apex crypto.Sign() method. The token generated in Python is working fine, while Apex generated token fails. 

This is a new area for me and imagine I am just missing something simple. Anyone have any ideas based on my sample code?

Hi All,

I am trying to deploy a flow using changesets and keep running into this error while validating below.
 

"Because the DoesRequireRecordChangedToMeetCriteria field has the value "true", you also need to set the following fields: Filters."

I have included every field that is referenced within the flow in the changeset. The only information I can even find on this "DoesRequireRecordChangedToMeetCriteria" variable is that it is now retrievable through the API. I have tried changing the starting conditions on the flow to even be very basic "everytime a record is created" without any conditional logic and still run into the same error. Does anyone have any idea as to what may be prompting this error? Thanks!

Hi All,

I am trying to deploy a flow using changesets and keep running into this error while validating below.
 

"Because the DoesRequireRecordChangedToMeetCriteria field has the value "true", you also need to set the following fields: Filters."

I have included every field that is referenced within the flow in the changeset. The only information I can even find on this "DoesRequireRecordChangedToMeetCriteria" variable is that it is now retrievable through the API. I have tried changing the starting conditions on the flow to even be very basic "everytime a record is created" without any conditional logic and still run into the same error. Does anyone have any idea as to what may be prompting this error? Thanks!