June 11, 2020

Connecting Elixir to Salesforce (Part 1)

Most of the time we are connecting to Salesforce via an Elixir API server written in Phoenix that receives requests from a Vue.js front-end to send to or retrieve data from Salesforce.

Connecting Elixir to Salesforce (Part 1)

It is not uncommon at Codedge to need to integrate with Salesforce in some form or fashion for the various projects we take on. Most of the time we are connecting to Salesforce via an Elixir API server written in Phoenix that receives requests from a Vue.js front-end to send to or retrieve data from Salesforce.

As with many integrations, the preferred way to gain access securely to a user's data on another service is to use OAuth. Since our situation is an Elixir back-end connecting to Salesforce we'll follow the OAuth 2.0 Web Server Flow for Web App Integration process from Salesforce.

Step 1 - Initiate OAuth

This process begins by retrieving the URL from Salesforce that your user can use to grant our application access to their data. In order to get this URL from Salesforce we execute an HTTP POST request to Salesforce authentication servers (See below).

def initiate(token) do
  url = "https://login.salesforce.com/services/oauth2/authorize"

  body =
    URI.encode_query(%{
      "client_id" => "YOUR_SALESFORCE_CLIENT_ID",
      "redirect_uri" => "AFTER_SUCCESS_REDIRECT_URL",
      "response_type" => "code",
      "state" => token
    })

  case HTTPoison.post(url, body, %{"Content-Type" => "application/x-www-form-urlencoded"}) do
  {:ok, %{status_code: 302} = resp} ->
    {_key, redirect_url} = List.keyfind(resp.headers, "Location", 0)
    {:ok, redirect_url}

  {:ok, resp} ->
    {:error, resp.body}
end

A couple of things to note about the above code block. The redirect_uri that you pass has to be a valid URL that Salesforce can send you the result of the authentication attempt to. Additionally, any redirect_uri sent to Salesforce must be whitelisted with them in your applications OAuth configuration (this is common for all OAuth procedures). Additionally, the state parameter allows us to encode any information about our context into the request so that Salesforce can pass it back to us after the user completes the OAuth login procedure. In our specific instance we are passing a token which is a JWT that contains the user's identifying information. We'll cover why this is important in a later step.

A successful response to our POST request will result in a response body with a status code of 302 and the URL to redirect our user to on a header key of Location. We treat anything other than this exact response as an error and pass it along to whatever part of the application attempted to invoke the initiate function for normal error handling.

Step 2 - Redirect User

The next step of the OAuth process is to redirect our user to the URL we parsed out of the first step's response. Since we are in the Phoenix API server context we will accomplish this via a Controller redirect call (see below).

def initiate_salesforce_oauth(conn, _params) do
  token = conn.assigns[:token]
  case initiate(token) do
    {:ok, redirect_url} ->
      redirect(conn, external: redirect_url)
    {:error, error} ->
      json(conn, %{success: false, error: inspect(error)})
  end
end

We extract the user's identifying JWT from the conn via conn.assigns[:token] and pass it into the previously covered initiate function so that when the user completes the login process with Salesforce and we receive the result, we are able to identify the user in a secure manner.

Step 3 - Consume OAuth Code

Assuming that the user successfully authenticates with Salesforce at the redirected URL location, our web server will receive the response of this authentication at whatever whitelisted redirect_uri we specified in the original initiation request. The key piece of data we are looking to receive back from Salesforce is a OAuth code which we can use to retrieve an OAuth access token. This access token is what we will use to actually make requests against the Salesforce API on the user's behalf. An example Phoenix controller method to accomplish this is below:

def oauth_callback(conn, %{"code" => code} = _params) do
  url = "https://login.salesforce.com/services/oauth2/token"

  body =
    URI.encode_query(%{
      "grant_type" => "authorization_code",
      "code" => code,
      "client_id" => "YOUR_SALESFORCE_CLIENT_ID",
      "client_secret" => "YOUR_SALESFORCE_CLIENT_SECRET",
      "redirect_uri" => "WHERE_TO_SEND_RESPONSE"
    })

    case HTTPoison.post(url, body, %{"Content-Type" => "application/x-www-form-urlencoded"}) do
      {:ok, resp} ->
        credential_data = Jason.decode!(resp.body)
        # Store or use this credential for accessing the Salesforce API
        json(conn, %{success: true})

      {:error, error} ->
        json(conn, %{success: false, error: inspect(error)})
    end
end

Above we use the code that was sent to our web-server as a result of the successful authentication by our user from the first step to request and receive an access credential for the user. The parsed body containing the credential data will look something like this:

%{
  "access_token" => "00D6g0000036JXv!ARoFFH.Dw1gtv9TjI8gYtNMQsoL4FgJGhqdzZR2zeTWkzfCRVsIyODtjXRd1dVdXDejQZgK2eHPw0myXYQvKRIW4wyNxcSE7",
  "id" => "https://login.salesforce.com/id/00D6g0000036JXvEAM/0056g000002CqVoAAK",
  "id_token" => "eyJraWQiOiIyMjIiLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdF9oYXNoIjoibU5jcjF4bmFveGd6REIxb0NVNzVzdyIsInN1YiI6Imh0dHBzOi8vbG9naW4uc2FsZXNmb3JjZS5jb20vaWQvMDBENmcwMDAwMDM2Slh2RUFNLzAwNTZnMDAwMDAyQ3FWb0FBSyIsImF1ZCI6IjNNVkc5TEJKTEFwZVhfUERzckNkS3QuSjd0VDlLWlBJdl96OWR4VmNQR0MwY05WN3E1YVlZeWJLQjl6bEtYZGIuT1l0WXFtMVZMTEZER29BTjFhaG0iLCJpc3MiOiJodHRwczovL2xvZ2luLnNhbGVzZm9yY2UuY29tIiwiZXhwIjoxNTkxMjk1MzEyLCJpYXQiOjE1OTEyOTUxOTJ9.oRkeIBvR27KUSxHBVgHILM_f6AQD5mzfGNnTKzBBGtLNWYQOCkQ-N2qHOqhk7-Us5rz3g5CxhTAORqOY-kZOzX70mVbPRbqknoNedPkMk6ENfVBk5LVQoL5tGAtbqJN7NySGuvByX2lA1t1oPYb1UxRrxSu0XvZNYmxH89XlZI_wKTEX0KaYt6Ot_esSlhOnVGLncn_5EpUUcZcZCK_gPeR6B2bYXJdXPps5XU_Rc-dPn-OHkNRrkct5h0zevD7OO1ZTN7RYa6EWysmHbwT6BkBjKAAAAI7ymbSQCIxcSxFeaMAtfAFT9_VomhiE6GebwMxs5xW9UptCq8ppVspvSq53w2NeVAGTwAz0ae_bwGi986lCYN9ha1E1F7KG0a8h_onqq68GS3r2oLH1ErNpPMUWAsdhSjNgNiGVc6El6GeQzTZUjNGz3DWDfbHmQUn21HJfP1z68y8gYvVv8ag_qNqM5BD2CHGV7mzxDHUjVZR9G0mdweI3gKXZ323Nq0Am2ngxHBpkPhRg_ZrWpI0EEtnOhhu-fiSfEunvYNdPIl5SPRsSFUSegtXWtGuCA-Vemvu-5pTRIhJCDIkbIp7mT1p0JYMW6Xz0lk-n4KpsRzhs9xOyxqcrC8xL9jJXyi1d86Zm9PDsnqIV7jXeRCCdZTNeC8zS4nw-B0Ija7XYOPQ",
  "instance_url" => "https://na552.salesforce.com",
  "issued_at" => "1591295192776",
  "refresh_token" => "5Aep8615B7FFFq3qblxpbycJl8ij4y_G5xGILPyGHsX1uga9IdPhSQ2fjruh9zZvan8vajsbvtMuET8piJgzisn",
  "scope" => "refresh_token openid id api",
  "signature" => "XTYSXim6Sth1jAqKY4as3bZU8IUisF6CxC7608lIU/A=",
  "token_type" => "Bearer"
}

With this data our Elixir API server can now access the Salesforce API on behalf of the user using the access_token you see above. At this stage of the flow, we usually persist the credential in a database so that we can easily make requests to Salesforce for the user without requiring them to complete the OAuth process every time they would like us to do something requiring Salesforce.

The entire OAuth procedure is now concluded and we can now access the Salesforce API. In my next post we'll cover how to actually utilize this OAuth access credential to make some basic requests against the Salesforce API to retrieve and create some "Leads" in Salesforce.