Sunday, November 18, 2018

Invoking Salesforce REST API using JWT

Invoking Salesforce REST API using JWT

Brief introduction to JWT

JSON Web Token(JWT) is a open standard way of API communication as JSON object which are digitally signed. JWT can be signed using a secret (HMAC algorithm) or a public/private key pair using RSA or similar algorithms.
JSON Web Tokens are created by three parts Header, Payload & Signature each separated by a dot. header.payload.signature 
The header contains of two different information in form of json object which is the type of token & the algorithm name being used in the communication. The json value is base64 url encoded before using.
{
"typ": "JWT"
"alg": "HS256",
}

The payload is also in json format and contains the claim information which is the user who is initiating the communication with additional details. The word claim indicates that the user is claiming that the information belongs to it. The json value is base64 url encoded before using. Following fields are standard fields which can be used as part of claim set by internet drafts (source - Wikipedia)
code name description
iss Issuer Identifies principal that issued the JWT.
sub Subject Identifies the subject of the JWT.
aud Audience Identifies the recipients that the JWT is intended for. Each principal intended to process the JWT must identify itself with a value in the audience claim. If the principal processing the claim does not identify itself with a value in the aud claim when this claim is present, then the JWT must be rejected
exp Expiration time Identifies the expiration time on or after which the JWT must not be accepted for processing. The value should be in NumericDate[10] format.
nbf Not before Identifies the time on which the JWT will start to be accepted for processing.
iat Issued at Identifies the time at which the JWT was issued.
jti JWT ID Case sensitive unique identifier of the token even among different issuers.
The signature is created by taking base64 url encoded header and payload and then creating the hash with the secret using the algorithm specified in the header.  The hash value is again base64 url encoded to get the signature. The signature value is used to make sure the value is not changed along the way.
The three values are appended with a dot to get the final token which can be sent over http for any reliable communication. The receiving end will verify the authenticity of the sender by using the public key to sign the data & comparing it with the signature.  The data inside JWT is encoded and signed and not encrypted so we should not pass any sensitive information with it. The purpose of JWT is to confirm and trust that the sent data was created by a authentic source.

Benefits of using JWT

1. When compared to Simple Web Tokens(SWT) & Security Assertion Markup Language Tokens(SAML) JWT uses JSON which occupies less size than XML.
2. SWT can only be symmetrically signed by a shared secret using the HMAC algorithm whereas JWT & SAML tokens can use a public/private key pair in the form of a X.509 certificate for signing.
3. In context of salesforce rest apis we dont need to store connected app secret or user password on whose behalf we are invoking the api.

Using JWT with Salesforce REST API

1. Create/import a certificate in your salesforce org 

Go to your salesforce org which will act as client and navigate to setup and then certificate and key management. You can create a self signed or CA signed certificate depending upon the requirement. you can also import certificate from external sources.

2. Create a connected app in the salesforce org

Go to your salesforce org which will act as client and navigate to setup and then app manager. Create a new connected app as shown in image below. We need to enable oauth settings and check use digital signature. We can download the certificate created in step 1 above from the certificate details page and upload here. You can copy the consumer key of the connected app to be used later.


3. Generate OAuth Bearer token to invoke REST Api

public class Jwt {
    private String iss{get;set;}
    private String sub{get;set;}
    private String aud{get;set;}
    private String tokenEndpointBaseUrl{get;set;}
    private String minutes{get;set;}
    private String certName{get;set;}

    public Jwt(String iss, String sub, String aud, String minutes, String certName, String tokenEndpointBaseUrl) {
        this.iss=iss;
        this.sub=sub;
        this.aud=aud;
        this.minutes=minutes;
        this.certName=certName;
        this.tokenEndpointBaseUrl=tokenEndpointBaseUrl;
    }


    public String getJwtBearerToken()
{
        String jwtHeader = '{"typ":"JWT","alg":"RS256"}';
        Long exp = DateTime.now().addMinutes(Integer.valueOf(minutes)).getTime();
        String jwtClaims = '{"iss":"' + iss + '","sub":"' + sub + '","aud":"' + aud + '","exp":' + exp + '}';
      /* Salesforce Apex doesnt have base64 encoding for url so we use Base64  and convert '+' to '-' and '/' with '_' */
        String jwtRequest = System.encodingUtil.base64Encode(Blob.valueOf(jwtHeader)).replace('+', '-').replace('/', '_') +
                '.' + System.encodingUtil.base64Encode(Blob.valueOf(jwtClaims)).replace('+', '-').replace('/', '_');
/* We are using certificate name with the Crypto.signWithCertificate api. If needed we can use Crypto.sign api which supports using the private key directly*/
        String signature = System.encodingUtil.base64Encode(Crypto.signWithCertificate('RSA-SHA256', Blob.valueOf(jwtRequest), certName)).replace('+', '-').replace('/', '_');
        String signedJwtRequest = jwtRequest + '.' + signature;

        String payload = 'grant_type=' + System.EncodingUtil.urlEncode('urn:ietf:params:oauth:grant-type:jwt-bearer', 'UTF-8');
        payload += '&assertion=' + signedJwtRequest;
        Http httpObj = new Http();
        HttpRequest req = new HttpRequest();
        HttpResponse res;
        req.setEndpoint(tokenEndpointBaseUrl+'/services/oauth2/token');
        req.setMethod('POST');
        req.setHeader('Content-Type', 'application/x-www-form-urlencoded');
        req.setBody(payload);
        res = httpObj.send(req);
        return res.getBody();
    }
}
public class RestCall{
public void invoke(){
        boolean isSandbox= isSandBoxEnv();
        String oauthBaseUrl=getOauthBaseUrl(isSandbox);
        String res = new Jwt('<consumer key>'
                '<user on whose behalf to invoke the api>',
                oauthBaseUrl,
                '3',
                '<Certificate Name defined in the org>',
                oauthBaseUrl
        ).getJwtBearerToken();
        JSONParser parser = JSON.createParser(res);
        String token='';
        while (parser.nextToken() != null) {
            if (parser.getCurrentToken() == JSONToken.FIELD_NAME
                    && parser.getText() == 'access_token') {
                parser.nextToken();
                System.debug('res='+ parser.getText().length());
                token=parser.getText();
            }
        }
        HttpRequest request = new HttpRequest();
        String url='<complete REST url >';
        request.setEndpoint(url);
        request.setTimeout(2 * 60 * 1000);
        request.setMethod('POST');
        request.setHeader('Authorization', 'Bearer '+token);
        request.setHeader('Content-Type','application/json');
        request.setBody(jsonBody);
        Http http = new Http();
        HttpResponse response = http.send(request);
  }      

public boolean isSandBoxEnv(){
        boolean isSandbox=false;
        List<organization> lstOrganization = [Select id,instanceName,isSandbox from Organization];
        if(lstOrganization.size()>0) {
            if(lstOrganization[0].isSandbox) {
                isSandbox=true;
            }
        }
        return isSandbox;
    }

    public String getOauthBaseUrl(boolean isSandbox){
        String url='https://login.salesforce.com';
        if(isSandbox){
            url='https://test.salesforce.com';
        }
        return url;
    }
}


4. Authorization by the user to the connected app

The user on behalf of whom we are going to invoke the API needs to authorize the connected app(one time)  using the following url in a browser for access by backend apex code. The user needs to login using the salesforce credentials.

https://login.salesforce.com/services/oauth2/authorize?
client_id=xxxxxxxxxx&
redirect_uri=https://login.salesforce.com/services/oauth2/success&
response_type=code
In case of use by front end javascript code, the authorization can be taken on the go using javascript & invoking the url in the same browser window. We need to use test.salesforce.com instead of login.salesforce.com for sandbox.

Author: Tarique Habibullah

Sunday, April 29, 2018

disabling aws ssh login using keys from *.pem files while using linux & create new user login with password using ssh

In this article we will see how we can use password based login instead of private keys in a ubuntu linux. When you create a linux instance in aws you are asked to save a private key to connect to the instance using ssh. This is how you connect fom your windows machine using putty or git bash

ssh -i 'c:\Users\Tarique\.ssh\MyKeyPair.pem' ubuntu@ec2-18-141-149-171.us-east-2.compute.amazonaws.com

Sometimes we may have a situation where we may need to have a direct password based access to the same instance. we will follow the following steps to achieve this


1. create new user to allow access to your ubuntu instance. it will prompt for unix password for new user & additional details.

    ubuntu@ip-172-31-9-45:~$ sudo adduser testfoo

2. optionally you can grant sudo access to the new user

    ubuntu@ip-172-31-9-45:~$ sudo usermod -aG sudo testfoo

3. change to root user to edit sshd_config file. we will take a backup before editing.

    ubuntu@ip-172-31-9-45:~$ sudo su
    
    root@ip-172-31-9-45:~$ cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak

    root@ip-172-31-9-45:~$ vi /etc/ssh/sshd_config

4. set PasswordAuthentication property to yes in the file. It is by default set to no in a new aws instance.

5. reload & restart sshd 

   root@ip-172-31-9-45:~$  sshd -t 

   root@ip-172-31-9-45:~$ service sshd restart

   root@ip-172-31-9-45:~$ exit

6.  connect your ubuntu instance using new user

ssh testfoo@ec2-18-141-149-171.us-east-2.compute.amazonaws.com    

Please note that ssh using keys are more secure and less prone to attacks. This alternative is only if you have some specific requirement. Please choose a complex password for your safety.


Author: Tarique Habibullah