[SF1] Connecting Salesforce with Amazon's Auth Code Grant

I've been tinkering with Amazon's Echo and the corresponding Alexa services since mine arrived slightly before Dreamforce. It became the cornerstone of my most complicated demo ever (which I will be repeating in May it appears). However, I have shied away from sharing too much real code because I've never really been happy with the way authentication worked against the Salesforce API's. The most reliable way was to setup an API user that could make the request with a stored username/password. While that isn't unusual for an integration like this, it lacks the personal touch of a user being able to link their Salesforce account with their Echo.

Amazon has been slowly upping their game in this department. First they add account linking in general and then just recently they added a more complete OAuth flow which includes both an access and refresh token. This allows a user to link their account once and keep accessing the API until the tokens are revoked.

After a couple of hoops, I think I have it working. Here's what I did.

First, go to your Alexa Skill in the Amazon Dashboard and add the ability to link accounts. Select the "Auth Code Grant" option. Using your Connected App settings in Salesforce, fill out the "Client ID" in the first part with your public key and then use the private key for the "Client Secret" in the token endpoint information below. Set the Authorization URL to Salesforce's login. This is what mine looks like:

setup

Note: When I took that screenshot, it worked fine. Mysteriously today it stopped adding the redirect uri. If that happens to you can just add it to the Authorization URL (just append "?redirect_uri=https://pitangui.amazon.com.etc").

Now you might notice something odd. In a perfect world, you could set the Token URI to be Salesforce's Token URI and pretty much just be done. However, there are two things in this imperfect world which will keep that from working:

  1. Amazon's request is a bit non-standard. It doesn't seem to include a Content-Type header and it seems to be in JSON and not form-encoded. Salesforce's Token URI requires both.

  2. The Salesforce response is also a bit non-standard. There doesn't seem to be an expires_in attribute and we also send over the instance url since it is required for the API requests.

To solve this, I've added an endpoint to my existing demo to basically act as a middle man between 1 and 2. My long term goal will be to make what I'm doing here a proper node package that other Salesforce developers can adopt to easily use OAuth in their projects but for the sake of expediency, here is endpoint using Express:

app.post('/token',function (req, res) {
  console.log(req.body);
  var sr = sync_request('POST', 'https://login.salesforce.com/services/oauth2/token',
                  {
                  headers: {'Content-Type':'application/x-www-form-urlencoded','Accept':'application/json'},
                  body: 'grant_type='+req.body.grant_type+'&code='+req.body.code+'&refresh_token='+req.body.refresh_token+'&client_id='+req.body.client_id+'&client_secret='+req.body.client_secret+'&redirect_uri='+req.body.redirect_uri
                  });
  console.log(sr.getBody('utf8'));
  response = JSON.parse(sr.getBody('utf8'));

  response.access_token = response.access_token + " " + response.instance_url;
  response.expires_in = 5400; //in seconds, set this to be less than your setting under session management.

  res.jsonp(response);
});

That endpoint is what I put into the token URI in the Amazon setup.

After that, you just split on the space to re-create the token and instance url:

if(req.body.session == null || req.body.session.user == null || req.body.session.user.accessToken == null) {
    send_alexa_response(res, 'Please log into Salesforce', 'Salesforce', 'Not Logged In', 'Error: Not Logged In', true);
   }

   oauth = {
        access_token : req.body.session.user.accessToken.split(" ")[0],
        instance_url : req.body.session.user.accessToken.split(" ")[1]
        }

Amazon will send along the most recent access token (with our instance url riding piggy back). You won't see the refresh token because you don't need it. Amazon will request a new access token via the same endpoint in an interval as defined by expires_in. So in my case it grabs one every 90 minutes to be sure it beats the 2 hour session timeout.

One thing to note: I cannot login to Salesforce using the Alexa app. I don't know why, I'm trying to get a conversation with Amazon going about it. It does work from a desktop browser though (http://echo.amazon.com).

Keep your eyes here on a more mature version of this solution.

comments powered by Disqus