OAuth using JWT on salesforce

  • Tutorial

OAuth JWT Flow Overview





JSON Web Token (JWT, pronounced as the English word jot) is an encrypted JSON security token that allows authentication and providing secure access to information from a third-party domain.

Skip the theory.

OAuth 2.0 JWT Bearer Token flow defines how a JWT token can be used to request an OAuth access token from Salesforce when the client wants to use the previously passed authorization. The data included in the JWT contains the username (username) for which you need an access token, as well as the identifier of the application that requests access (client_id). Authentication of the host application is provided by applying a digital signature to the JWT - i.e. The json token must be encoded using the RSA key.

JWT Bearer Token stream supports RSA / SHA256 algorithm, i.e. you must download the signed certificate in the configuration page of your Connected App, this will allow Salesforce to verify that your token came from a trusted source.

When using the JWT Bearer Token stream, the JWT token itself is sent using the POST request (1) to the url called OAuth endpoint - login.salesforce.com/services/oauth2/token and contains the following data (payload)

grant_type = urn% 3Aietf% 3Aparams % 3Aoauth% 3Agrant-type% 3Ajwt-bearer & assertion = PHN ... QZT
grant_typeSet this to urn: ietf: params: oauth: grant-type: jwt-bearer
assertionThe JWT bearer token.


The server checks the JWT and creates an access token (access_token) (2), based on the previously granted access to the application. However, the client side is not required to store refresh_token (as with other OAuth streams), in this case the client application generates a new JWT token when it is necessary to obtain access_token. Also note that client_secret is not necessary when sending a request for a token - JWT authorizes the application.

You can read more theory about OAuth 2.0 JWT Bearer Token Flow and see an example on java here - OAuth 2.0 JWT Bearer Token Flow .


Private key and certificate generation



JWT Bearer Flow requires RSA SHA256 certificate (X.509). This pair can be generated using the openssl utility. Below is an example of a command line for generating a key and certificate
openssl req -x509 -nodes -subj "/C=US/ST=CA/L=San Francisco/CN=web_site.com" -days 3650 -newkey rsa:2048 -keyout your_private_key.pem -out your_certificate.pem




your_private_key.pem - will be used in the client application
your_certificate.pem - will be uploaded to the Connected app on salesforce (where you will log in)

Attention! The key and certificate listed below is just an example. Do not try to use them in production.

your_private_key.pem
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA9aHYIsb3WWWfhSeDdZnZagxRrHvcam1QZc5QCGr1EyiuBedb
9xCdJx9qXKv7qvF0fTxZU5wUVicgvUYrTg9hBxx2rkZ4GKgy6DQSO3A/d/7CUk/d
6BrIQlYLW5iLZ68aN7RoVdTl8+wMRjW6yuAIl02bpeI1yuLFDL7jM9mA+PEtSRcB
4JrbvfKoiyG8Q0PWXpl1DyjpqjmP02ezGlUe9MwiZQ6w3MfR0tMOAaroYKo+Tpqp
yDy98ca6cwOr1G7H46n/ifITJp1QyWPUA8P0/Bu/R2nLhsec5Xp5FOJgIn4tOysJ
oAaZRAmg+OMMn7IosiOOtzxWOFROYP89zUwICQIDAQABAoIBAQDSAuNJsLXQ0EtY
TFVgWf8CZa/6+heVQwtHT+Mrn08UZ2aKk6oUjjbfOUP2IfFV0YWYgd2Z18uncWLT
kUf7QLjjnJl4HbYW1tBXqhWgjjMlxEKen7yJ80QfO3QQh1dBefu/ftbp89H8AGjq
KowfYvbVbjxS0xrM8aAqapbVQPS9YcJGm4VvzE+H+p/cdORfmcssBZtX8ym/EeGB
hOWmmQyHxrfR5SzxWyGwntT7kMbZMJ5rixiUnDUhHgTSOWbKYTKPE5sojrV8en3r
Ic/Sy/GqwHPfDhaA/ZfPI7d8wBueDrMOb4goO6oNmO6xQkozSOgIsOmpHOpkEaj/
Vlbqmw7RAoGBAP+GLKFSPSgKt5ZymRieZmPXAYohYIcDAExSYdQOA6vVlheXn9Kr
/fHZLNult0omevCb7stW192H2A2RN5HzqUO8eJ0/DyUR13ZluNGUUxLMh//wapKT
wfCXzDMYdYoeVf6PgZwLdY2BSXG3/Ycr7N27ak251Nffgnh4RxXW8eL1AoGBAPYW
9CweiJ1Vt5Z3KHDRsoCWqFYH+229j9tcxdJGt5VNKrkDxOJfhGu3N6dESo8qEGuu
ix6hZhMnF04s8oiBowXFfoopw4K3CUR9paaM+W1fsKDM0f51wZAxd2ZmKnNZW53a
zc5iQH0Zy1qT9M5iiXM3VutadSkwupETlOqRYOxFAoGAEf5Z0DZhVhuDGBYTz9b/
sNIoKpj1GizM7ZLzjqI6AfS1cA3eVFCGPmyjqwf9YzxYde8VHr6Lzu7M+Q+b0SxO
ZBW2jKQvJdYezRiWrjN6sh0zCoPcjVvYUV/vIj37sPE37wgeAWYRLhjHmjlxof4m
3Opgrv6CDX2Qy3j00IXlXK0CgYAU9smgRI2g0Z+NKuOAEO0i9TKr+Ywawi5SIqob
iriy+FruXfrUygxO3NHZ5wBvB8dUVQ828crvUMI0f7G9nUWVBUkNXhdwuEUK16VX
9eR9w8wZNrmg8skljoE8cPGm1/LtFKm5rjcOMTdYpQgS2OQas5ks/YzDkIokN8XU
4cOe/QKBgFp2xSur5d4OFm3yfwq9I2p+tLQrKQexgPExbs4opPmGvsg429xmiLDs
d1V0622jOeZflsSFJpsY/MPgxMM2mEfjfEWmqcyOHKoCGbsBc4HC7UJ5zXo4Txaz
1dU9+mRV9XS7mr5bSgNyRB3X1DEeJ4Wi8tp3gV4FH0DwSNGByS/1
-----END RSA PRIVATE KEY-----


your_certificate.pem
-----BEGIN CERTIFICATE-----
MIIDwTCCAqmgAwIBAgIJAOuaqw3NQvmyMA0GCSqGSIb3DQEBBQUAMEkxCzAJBgNV
BAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEVMBMG
A1UEAxQMd2ViX3NpdGUuY29tMB4XDTE0MTIxODE0MzkxM1oXDTI0MTIxNTE0Mzkx
M1owSTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJh
bmNpc2NvMRUwEwYDVQQDFAx3ZWJfc2l0ZS5jb20wggEiMA0GCSqGSIb3DQEBAQUA
A4IBDwAwggEKAoIBAQD1odgixvdZZZ+FJ4N1mdlqDFGse9xqbVBlzlAIavUTKK4F
51v3EJ0nH2pcq/uq8XR9PFlTnBRWJyC9RitOD2EHHHauRngYqDLoNBI7cD93/sJS
T93oGshCVgtbmItnrxo3tGhV1OXz7AxGNbrK4AiXTZul4jXK4sUMvuMz2YD48S1J
FwHgmtu98qiLIbxDQ9ZemXUPKOmqOY/TZ7MaVR70zCJlDrDcx9HS0w4Bquhgqj5O
mqnIPL3xxrpzA6vUbsfjqf+J8hMmnVDJY9QDw/T8G79HacuGx5zlenkU4mAifi07
KwmgBplECaD44wyfsiiyI463PFY4VE5g/z3NTAgJAgMBAAGjgaswgagwHQYDVR0O
BBYEFComQlVarS6Y5vru8+0WVLEN5fkeMHkGA1UdIwRyMHCAFComQlVarS6Y5vru
8+0WVLEN5fkeoU2kSzBJMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNV
BAcTDVNhbiBGcmFuY2lzY28xFTATBgNVBAMUDHdlYl9zaXRlLmNvbYIJAOuaqw3N
QvmyMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAN4bpIEbkYe/fkMZ
gLplj5wVz4CQgTeYdjgQnZqlJV7bXUJnEREE1oyfS5UHHvKpAOzBhZFNc+T0LFV1
ivKBa6ZJnMY9iUqDuyf7x3QyV62bWeHqOuT8RcZpMtJ1YcQUZgotWlvm2/4zlbVE
sZNhsC8NxPRGu3T9d+GPa2zfICMMwDGOHJejPH0FBxdbByHDRkg59UYLFWhejY1G
qN2QRDOi6B8LytcMkMyiPZoXL8mFOhR9C6GdoLcc1IZZk5PnrAn0i8sgBs9d4ev1
JU42f8cI8QSG/xKftdVRsoSBPG+EGXJ4EmjUXt1XMu9zSij5WZP5mHfzdfkLgwnf
XFhp0s0=
-----END CERTIFICATE-----


Salesforce org configuration



  1. Setup-> Create-> App -> scroll down to Connected Apps -> click New

  2. Configuring the Connected app and downloading the certificate generated in the first step

  3. Save Changes.
  4. Open the application settings page Setup -> Manage Apps -> Connected App -> select your application and click the Edit button

  5. Select the option All users may self-authorize for Permitted Users
  6. Select the option Relax IP Restrictions for IP Restrictions
  7. Click Save


JWT Authorization Process



In order to allow a connection from one salesforce organization to another salesforce organization, you need to register Remote Site Settings on the client side.
Setup -> Security controls -> Remote Site Settings -> New


To allow access to a specific application for a specific user, you will need to link and open a specific link in the browser. Its format is as follows:

https://login.salesforce.com/services/oauth2/authorize?client_id=[client_id]&redirect_uri=[redirect_url]&response_type=code

client_idthe so-called consumer key generated during the creation of the Connected App, in our case 3MVG9QDx8IX8nP5RvIRcKYiylTgtQ1RUJT7tgOC0REp4bEA3JwFM5t39RGF_H5Kwfh6hf_j3.XKTf.IMOTw1
redirect_urlURL to which the redirect will occur (it must also match what was specified when creating the Connected App), in our case login.salesforce.com/home/home.jsp




To get the access token, we will need to create a JWT and sign it using the RSA SHA256 key generated in the first step.

Below is the Apex code for generating JWT and getting access_token


public with sharing class  JwtAuth {
    //JWT Header
    public class JwtHeader{
        public String alg = 'RS256';
    }
    //JWT Claims Set
    public class JsonClaimsSet{
        public String iss; //Consumer Key of Salesforce Connected App
        public String prn; //Salesforce username on destination Org
        public String aud = 'https://login.salesforce.com'; //https://login.salesforce.com OR https://test.salesforce.com
        public String exp = '' + System.currentTimeMillis() + (1000 * 60 *60); //Token expiration date - timestamp in miliseconds
        public String sub; //optional in JWT Bearer Token flow
    }
    // JWT Auth flow result
    public class JwtAuthResult {
        public String scope;
        public String instance_url;
        public String token_type;
        public String access_token;
        public String error;
        public String error_description;
    }
    // Singleton   
    private static JwtAuth self;
    private JwtAuth () {}
    public static JwtAuth getInstance() {
        if (self == null) {
            return new JwtAuth();
        }
        return self;
    }
    public String generateJWT(String consumerKey, String sfUsername, String x509PrivateKey) {
        //lifetime by defaultt = 10 minutes
        //connect to production by default
        return generateJWT(consumerKey, sfUsername, x509PrivateKey, 600, false);
    }
    private String generateJWT(String consumerKey, String sfUsername, String x509PrivateKey, Integer lifetimeSeconds, Boolean isSandbox) {
        //1. Construct a JWT Header
        JwtHeader header = new JwtHeader();
        header.alg = 'RS256';
        String jwtHeaderStr = JSON.serialize(header);
        jwtHeaderStr = base64EncodeUrl(jwtHeaderStr);
        //2. Construct a JSON Claims Set
        JsonClaimsSet claimsSet = new JsonClaimsSet();
        claimsSet.iss = consumerKey;
        claimsSet.prn = sfUsername;
        claimsSet.aud = isSandbox ? 'https://test.salesforce.com' : 'https://login.salesforce.com';
        claimsSet.exp = '' + (System.currentTimeMillis() + lifetimeSeconds*1000);
        String jsonClaimsSetStr = JSON.serialize(claimsSet);
        jsonClaimsSetStr = base64EncodeUrl(jsonClaimsSetStr);
        //3. Join JWT Header and JSON Claims Set
        String token = jwtHeaderStr + '.' + jsonClaimsSetStr;
        //4. Sign with RSA-SHA256 Certificate
        String signedPayload = base64EncodeUrl(
            Crypto.sign('RSA-SHA256', Blob.valueOf(token), EncodingUtil.base64Decode(x509PrivateKey))
        );
        return token + '.' + signedPayload;
    }
    public JwtAuthResult requestAccessToken (String endpoint, String jwtSignedToken) {
        HttpRequest req = new HttpRequest();
        req.setEndpoint(endpoint);
        req.setBody(
            EncodingUtil.urlDecode('grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer', 'UTF-8')
            + '&assertion=' + jwtSignedToken);
        req.setHeader('Content-Type', 'application/x-www-form-urlencoded');
        req.setMethod('POST');
        Http http = new Http();
         HTTPResponse res = http.send(req);
        String result = res.getBody();
        return (JwtAuthResult) JSON.deserialize(result, JwtAuth.JwtAuthResult.class);
    }
    //Convert Plain base64 to base64Url
    public String base64EncodeUrl(Blob data) {
        return EncodingUtil.base64Encode(data).replaceAll('\\+','-').replaceAll('/','_');
    }
    //Convert Plain base64 to base64Url
    public String base64EncodeUrl(String data) {
        return EncodingUtil.base64Encode(Blob.valueOf(data)).replaceAll('\\+','-').replaceAll('/','_');
    }
}


Useful Resources:

Digging Deeper into OAuth 2.0 on Force.com
Remote Access. Oauth JWT flow
Apex Crypto Documentation
Apex Crypto Examples

Also popular now: