0

I am currently working on the project that I am going to integrate the application of my company and salesforce.

In my case, it seemed that using the JWT for authentication is better. So, I wanted to try it.

but I don't know how to generate JWT and send the proper request to salesforce on Ruby though I read docs.

What I wanted to do is that

1, create application on salesforce (done)

2, create X509 certification and set it on the application on salesforce. (done)

3, create JWT by using the secret key of X509 certification. (I think I've done it )

4, send post request with JWT parameter included in assertion params and grant_type(grant_type= urn:ietf:params:oauth:grant-type:jwt-bearer&) (I got an error)

when I send post request the errors says {"error":"invalid_grant","error_description":"invalid assertion"} so it occurs certainly because of the parameter I sent.

the code I randomly wrote is something like this.

require 'jwt'
require 'json'
require 'net/http'
require 'uri'

payload = {
    "sub": "abel@example.com", ← my account on salesforce
    "iss": "3MVG9pe2TCoA1PasbdvjabsodyoQFZTn0Rjsdbfjbasojdbn;oajs", ← the consumer key of the application on salesforce.
    "aud": "https://test.salesforce.com"
  }

  public_key = Base64.urlsafe_encode64(
  'USqTxNC7MMIeF9iegop3WeDvFL
  075JSUECgYEA76FNJLeStdq+J6Fj2ZBYdDoeuDHv3iNA0nnIse9d6HnjbdrdvjmV
  rT1CJuHh9gnNKg4tyjkbpc9IVj4/GF0mNUCgYEAynvj
  qOYCzts4W7Bdumk6z8QULJ5QoYCrAgFtwra9R1HDcxTz+GPgJOVx2QBX+aQbDOaD
  WV1s9WqE0/Lfi/VVUEzg1hZ8326buGRk1DRVG2Oa48==') ← this is public_key example of the certification.


rsa_private = OpenSSL::PKey::RSA.generate 2048
rsa_public = rsa_private.public_key

token = JWT.encode payload, rsa_private, 'RS256'

puts token

decoded_token = JWT.decode token, rsa_public, true, { algorithm: 'RS256' }

puts decoded_token

post =  {
    'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
    'assertion': token
}

  uri = URI.parse('https://login.salesforce.com/services/oauth2/token')
  https = Net::HTTP.new(uri.host, 443)
  https.use_ssl = true
  response = https.post(uri.path, post.to_query)
  print response.body

the PHP version of what I want to achieve is something like this.

<?php
    
    require_once './vendor/autoload.php';
    
    use Lcobucci\JWT\Builder;
    use Lcobucci\JWT\Signer\Key;
    use Lcobucci\JWT\Signer\Rsa\Sha256;
    
    // login URL
    // production: https://login.salesforce.com
    // Sandbox: https://test.login.salesforce.com
    define('LOGIN_URL', 'https://test.salesforce.com');
    //consumer key
    define('CLIENT_ID', <<consumer key of the application on salesforce>>);
    //user ID
    define('USER_ID', 'xxxxx@example.com');
    
    function createjwt() {
    
       $signer = new Sha256();
       $privateKey = new Key('file://cert/server.key'); ← probably the key from certification
       $time = time();
       
       $token = (new Builder())->issuedBy(CLIENT_ID) // iss: consumer key
                               ->permittedFor(LOGIN_URL) // aud: Salesforce login URL
                               ->relatedTo(USER_ID) // sub: Salesforce user ID
                               ->expiresAt($time + 3 * 60) // exp: within three mins
                               ->getToken($signer,  $privateKey);
    
       return $token;
    }
    
    $jwt = createjwt();
    
    echo $jwt;


function auth() {
   $jwt = createjwt();

   $post = array(
       'grant_type' => GRANT_TYPE,
       'assertion' => $jwt,
   );

   $curl = curl_init();
   curl_setopt( $curl, CURLOPT_URL, AUTH_URL );
   curl_setopt( $curl, CURLOPT_RETURNTRANSFER, 1 );
   curl_setopt( $curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1 );
   curl_setopt( $curl, CURLOPT_POSTFIELDS, $post );
   $buf = curl_exec( $curl );
   if ( curl_errno( $curl ) ) {
       exit;
   }
   curl_close( $curl );
   
   $json = json_decode( $buf );

   $accinfo = array(
       // URL to access
       'instance_url' => $json->instance_url,
       // Bearer token in order to access
       'access_token' => $json->access_token,
   );

   return $accinfo;
}

$accinfo = auth();

EDIT

I changed a code a lot. But I still have different error that says 'initialize': Neither PUB key nor PRIV key: nested asn1 error (OpenSSL::PKey::RSAError)' around @private_key definition.

I read this and tried changing the string in private_key.pem to in one line but I didn't work ( maybe I did in a wrong way) and didn't understand the meaning of incorrect password (mentioned as the second answer) What causes "Neither PUB key nor PRIV key:: nested asn1 error" when building a public key in ruby?

  def initialize
    @cert_file = File.join(File.dirname(__FILE__), *%w[private_key.pem])
    # @cert = Base64.urlsafe_encode64(@cert_file)
    # print @cert_
    # @cert_file = File.join(File.dirname(__FILE__), *%w[server.csr])
    @base_url = "https://test.salesforce.com"
    @auth_endpoint = "/services/oauth2/authorize"
    @token_request_endpoint = "/services/oauth2/token"
    @token_revoke_endpoint = "/services/oauth2/revoke"
    @username = "my username"
    @client_id = "pe2TCoA1~~~~" client_id
    @private_key = OpenSSL::PKey::RSA.new(File.read(@cert_file))
    # @private_key = OpenSSL::PKey::RSA.generate(private_key)
    @rsa_public = @private_key.public_key
    # @private_key = OpenSSL::PKey::RSA.new(File.read(@cert_file))
  end
  def claim_set
    {
      iss: @client_id,
      sub: @username,
      aud: @base_url,
      exp: (Time.now + 3.minutes).to_i.to_s
    }
  end

  def jwt_bearer_token
    JWT.encode(self.claim_set.to_s, @rsa_public, 'RS256')
  end

  def request_auth
    post = {body: {grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer", assertion: jwt_bearer_token}}
    uri = URI.parse("#{@base_url}#{@token_request_endpoint}")
    https = Net::HTTP.new(uri.host, 443)
    https.use_ssl = true
    response = https.post(uri.path, post.to_query)
    print response.body

  end
  Salesforce.new.request_auth
end

Any advices are appreciated. Thank you

Abel
  • 113
  • 2
  • 9
  • Is there any documentation or tutorials from Salesforce which show how to do it? Shouldn't you register the public key somewhere in Salesforce so it can properly validate your assertion token? – Michal Trojanowski Jun 24 '21 at 06:29
  • thank you. you are right and I searched and found better solution. but I still got an error. It is about RSA and I don't know how to solve this. – Abel Jun 24 '21 at 07:32

0 Answers0