Search

Developing Alexa Skills Locally with Node.js: Account Linking Using OAuth

Juan Pablo Claude

13 min read

May 12, 2016

Developing Alexa Skills Locally with Node.js: Account Linking Using OAuth

Editor’s note: This is the sixth post in our series on developing Alexa skills.

One of the greatest features of Alexa is that it functions as a personal assistant you can interact with without having to physically touch the device. This allows you to get information or accomplish tasks while you are, for example, baking a cake. One of the tasks you could accomplish in such a sticky situation could be to post a tweet about your baking adventures.

From an Alexa developer’s point of view, the task of posting a tweet is a pretty sophisticated operation because the skill needs to authenticate with the user’s Twitter account on the web, then get authorization to access the API in order to make a posting.

From a convenience and security point of view, it would be a terrible idea for the skill to ask for the user’s credentials verbally every time access to the Twitter API is needed. Furthermore, an Alexa-enabled device does not have a way to store these credentials locally, so another approach must be used.

Fortunately, the Alexa Skills Kit features account linking, which lets you access user accounts on other services, Twitter among them, using the OAuth protocol. In this post, we will use account linking and OAuth to grant delegated authority to our Airport Info skill so that it can post an airport’s flight status to a user’s Twitter account. Delegated authority means that the Airport Info skill will be granted permission to post to the user’s Twitter account without ever having access to the actual account credentials.

Note that Alexa uses the OAuth 2.0 protocol, and some services like Twitter still use version 1.0. The differences in the implementation are not great. Essentially, dealing with OAuth 1.0 requires an additional token request step that will be handled in this exercise by a separate web application.

Flight status

Registering Airport Info as a Twitter App

If you haven’t already built an Alexa Skill, check out our previous posts on building Airport Info to get started.

The first step in enabling Twitter delegated authority to the Airport Info skill is to let Twitter know that the skill exists. We must register Airport Info as a Twitter App, so that Twitter knows the skill will later ask for authorization to post on a user’s behalf. To accomplish this, first log in to your Twitter account and visit the Twitter Apps page.

Twitter Apps

Now click on the “Create New App” button to see the application details page. In the “Name” field, enter a descriptive name for the skill. Note that this name needs to be unique across the entire Twitter Apps namespace, for all developers. Therefore, you may need to choose a name other than what is seen in the figure below. Any name collisions will result in the following error message: “The client application failed validation: Name has already been taken.”

In the “Description” field, enter a short string of text describing your Twitter app. It will be displayed when a user grants the skill the authority to post on Twitter.

In the “Website” field, enter a URL that users can visit to get more information about your skill and how it uses Twitter. In this case, we will enter “https://alexa-twitter-airport-info.herokuapp.com/app”.

This link corresponds to a Heroku-hosted web app we created for this post. It serves as a simple OAuth client or intermediary you can use for this experiment without having to host your own application, which could be an AWS Lambda function just like your skill. Note that this application has a very minimal web interface. You can also get the code for this app from GitHub.

Here’s what the “Create an application” page should look like at this point:

Create an application form

The final field is a “Callback URL”. This is the URL that will be loaded after a new user grants delegated authority to Airport Info by authenticating with Twitter. Once again, you will use the alexa-twitter-airport-info OAuth web app described above for this purpose. (The details of what happens when this URL is called back will be explained a little later on in this post.)

Finish the registration by accepting the developer agreement and clicking on the “Create your Twitter application” button. You will be redirected to the details page for your new application. Click on the “Keys and Access Tokens” tab to view the Consumer Key and Consumer Secret for your application.

Copy the Consumer Key (API Key) and Consumer Secret (API Secret) as you will be needing them soon.

Twitter keys

The Consumer Key and the Consumer Secret will be used to authenticate the Airport Info skill when it needs to request authorization to tweet on the user’s behalf.

Finally, click on the permissions tab and set the “Access” type to “Read, Write and Access direct messages”. Click on the “Update Settings” button to save your change.

Twitter app permissions

Configuring the Skill to Use Account Linking

Now that Twitter has been informed that Airport Info may ask for API access, you need to configure the skill to use that privilege. Log in to the Alexa Skills Developer Portal and display your skills list. Assuming that you have already built and staged the Airport Info skill, click on its name on the list to display its settings, and then advance to the “Configuration” stage. Enable account linking by clicking on the “Yes” radio button. After you make that selection, additional fields will appear.

Configure account linking

“Authorization URL” is one of the new fields activated by enabling account linking. This is a crucial bit of information because it is the URL a user will be directed to in order to grant delegated authority to the skill. In this particular case, redirecting to this URL should result in the Twitter log-in being displayed to the user. As the user authenticates with Twitter, the OAuth workflow is triggered, and an access token is created for the user and stored with Amazon.

Once again, you will use the alexa-twitter-airport-info OAuth web app as an intermediary to manage the OAuth workflow. This OAuth web application is designed in such a way that you and any other Alexa developer trying this exercise can use it instead of deploying their own OAuth app. The compromise in achieving this multi user flexibility is that you will have to pass developer-specific information such as the Twitter App Consumer Key and Consumer Secret when you call the OAuth web app URL. Even though the URL with the Twitter App information will be called with HTTPS, you would not want to do this in a production implementation. In that case, you would have to keep the Consumer Key, Consumer Secret and any other OAuth information securely stored in the hosting server.

The “Authorization URL” field contains the URL that has to be called on the alexa-twitter-airport-info OAuth web app with the Twitter App key, secret and vendor id.

  • Enter: “https://alexa-twitter-airport-info.herokuapp.com/oauth/request_token?vendor_id=XXXXXX&consumer_key=YYYYYY&consumer_secret=ZZZZZZ”.
  • Replace “XXXXXX” with the vendorId value found on the “Redirect URL” field. The field contains a URL equivalent to: “https://pitangui.amazon.com/spa/skill/account-linking-status.html?vendorId=XXXXXXX”.
  • The “YYYYYY” placeholder corresponds to the Twitter App Consumer Key.
  • The “ZZZZZZ” placeholder corresponds to the Twitter App Consumer Secret.

In the “Privacy Policy URL” field, you should indicate a web page that would describe your skill’s privacy policy. Enter “https://alexa-twitter-airport-info.herokuapp.com/policy” for this test scenario. Click on the “Save” button to finish configuring the skill.

The Account Linking Process

As you have finished configuring the Airport Info skill for account linking, you are now ready to walk through every step of the process.

As a new Airport Info user enables the skill for an Alexa device, the Alexa App will redirect them to the Authorization URL you specified in the previous section. This URL corresponds to the /oauth/request_token endpoint of the alexa-twitter-airport-info application. Calling this endpoint with the appropriate information (Consumer Key, Consumer Secret, Vendor ID) generates a request for Twitter to return an access token. This request results in a redirection to a Twitter authentication page that asks a user to enter a username and password.

As the authentication is completed, Twitter generates an access token and redirects to the callback URL we specified when configuring the Twitter App. This URL corresponds to the /oauth/callback endpoint of the alexa-twitter-airport-info OAuth web app. This endpoint redirects to the “Redirect URL” specified in the skill’s account linking page with the Twitter access token just generated. This token does not expire and is saved for the particular skill user with Amazon. The token is now available within the Airport Info code as you will see shortly.

The entire account linking process is described in the diagram below.

Alexa skill account linking process

Putting Account Linking to Work: A tweetAirportStatusIntent Handler


As mentioned earlier, the Twitter access token is available within the skill’s code, specifically from the sessionDetails object available from the request passed to a handler function. This will allow you to implement a new intent handler to post information to Twitter.

Open index.js in your Airport Info skill local development project and add the following new handler (here’s more info on implementing the skill):

app.intent('tweetAirportStatusIntent', {
'slots': {
    'AIRPORTCODE': 'FAACODES'
},
// Add  ‘tweet’ to utterances:
'utterances': ['tweet {|delay|status} {|info} {|for} {-|AIRPORTCODE}']
},
function(request, response) {
    var accessToken = request.sessionDetails.accessToken;
    if (accessToken === null) {
        //no token! display card and let user know they need to sign in
    } else {
        //has a token, post the tweet!
    }
});

Notice how the availability of request.sessionDetails.accessToken is tested. If it is not available, the user must be informed that they must link accounts. Update the code as shown below:

function(request, response) {
    var accessToken = request.sessionDetails.accessToken;
    if (accessToken === null) {
        response.linkAccount().shouldEndSession(true).say('Your Twitter account is not linked.
        Please use the Alexa App to link the account.');
        return true;
    } else {
        //has a token, post the tweet!
    }
});

The response.linkAccount() method displays an appropriate card on the Alexa App to guide the user on how to link accounts. This is the same card displayed when you enable the skill for the first time.

A Twitter Helper Object


The logic for posting airport status information will be handled by a new helper object called TwitterHelper. In turn, this helper object will rely on the Twit Node.js package. To install Twit, open a terminal console and navigate to the airportinfo folder within your alexa-app-server directory used for local development. Once in the airportinfo directory, issue the following command to install Twit:

$ npm install twit --save

Now create a new file in the airportinfo directory called twitter_helper.js and type the code below.

'use strict';
module.change_code = 1;
var _ = require('lodash');
var Twitter = require('twit');
var CONSUMER_KEY = 'XXXXX';
var CONSUMER_SECRET = 'XXXXX';

function TwitterHelper(accessToken) {
    this.accessToken = accessToken.split(',');
    this.client = new Twitter({
        consumer_key: CONSUMER_KEY,
        consumer_secret: CONSUMER_SECRET,
        access_token: this.accessToken[0],
        access_token_secret: this.accessToken[1]
    });
}

TwitterHelper.prototype.postTweet = function(message) {
    return this.client.post('statuses/update', {
        status: message
    }).catch(function(err) {
          console.log('caught error', err.stack);
        });
};

module.exports = TwitterHelper;

Be sure to replace the “XXXXX” placeholders with your Twitter App Consumer Key and Consumer Secret.

The TwitterHelper() function accepts the access token and creates an instance of the TwitterHelper object with authorization to post. The postTweet() method can then be used to post text to the user’s Twitter account.

Posting Tweets

Open index.js and add the following line of code right below any other require() call.


var TwitterHelper = require(./twitter_helper);

Now you can complete the implementation of the tweetAirportStatusIntent with the code below. Please note the whole intent is listed and the new code is only within the else clause as indicated.

app.intent('tweetAirportStatusIntent', {
'slots': {
    'AIRPORTCODE': 'FAACODES'
},
'utterances': ['tweet {|delay|status} {|info} {|for} {-|AIRPORTCODE}']
},
function(request, response) {
    var accessToken = request.sessionDetails.accessToken;
    if (accessToken === null) {
        response.linkAccount().shouldEndSession(true).say('Your Twitter account is not linked.
        Please use the Alexa app to link the account.');
        return true;
    } else {

        // New code begins here:
        // I've got a token! make the tweet.
        var twitterHelper = new TwitterHelper(request.sessionDetails.accessToken);
        var faaHelper = new FAADataHelper();
        var airportCode = request.slot('AIRPORTCODE');
        if (_.isEmpty(airportCode)) {
            var prompt = 'i didn't have data for an airport code of ' + airportcode;
            response.say(prompt).send();
        } else {
            faaHelper.getAirportStatus(airportCode).then(function(airportStatus) {
                return faaHelper.formatAirportStatus(airportStatus);
            }).then(function(status) {
                return twitterHelper.postTweet(status);
            }).then(
                function(result) {
                    response.say('I've posted the status to your timeline').send();
                }
            );
            return false;
        }
        // New code ends here.
    }
});

Updating the Skill Service Code

Now you need to replace the code in your AWS Lambda function with the code for the Tweeter-enabled version of the Airport Info skill.

Create a new zip archive with all the contents of the airportinfo folder, and then go to the AWS Lambda console. Click on the Airport Info function and then click on the “Upload” button. Select your archive and update the skill.

Updating the Skill Service Code

Updating the Skill Interaction Model

You have just updated the skill code, but you still need to update the skill’s interaction model because you added a new intent and new utterances. Go back to your skills list in the Amazon Developer Console and click on the Airport Info skill. Then advance to the “Skill Interaction” page.

Copy the schema below and paste it to the “Intent Schema” field on the “Interaction Model” page.

{
  "intents": [
    {
      "intent": "tweetAirportStatusIntent",
      "slots": [
        {
          "name": "AIRPORTCODE",
          "type": "FAACODES"
        }
      ]
    },
    {
      "intent": "airportInfoIntent",
      "slots": [
        {
          "name": "AIRPORTCODE",
          "type": "FAACODES"
        }
      ]
    }
  ]
}

Updated Intent Schema

Next, copy the updated utterances list including the tweetAirportStatusIntent and paste it to the “Utterances” field on the “Interaction Model” page.

tweetAirportStatusIntent    tweet {AIRPORTCODE}
tweetAirportStatusIntent    tweet delay {AIRPORTCODE}
tweetAirportStatusIntent    tweet status {AIRPORTCODE}
tweetAirportStatusIntent    tweet info {AIRPORTCODE}
tweetAirportStatusIntent    tweet delay info {AIRPORTCODE}
tweetAirportStatusIntent    tweet status info {AIRPORTCODE}
tweetAirportStatusIntent    tweet for {AIRPORTCODE}
tweetAirportStatusIntent    tweet delay for {AIRPORTCODE}
tweetAirportStatusIntent    tweet status for {AIRPORTCODE}
tweetAirportStatusIntent    tweet info for {AIRPORTCODE}
tweetAirportStatusIntent    tweet delay info for {AIRPORTCODE}
tweetAirportStatusIntent    tweet status info for {AIRPORTCODE}
airportInfoIntent    {AIRPORTCODE}
airportInfoIntent   flight {AIRPORTCODE}
airportInfoIntent   airport {AIRPORTCODE}
airportInfoIntent    delay {AIRPORTCODE}
airportInfoIntent   flight delay {AIRPORTCODE}
airportInfoIntent   airport delay {AIRPORTCODE}
airportInfoIntent    status {AIRPORTCODE}
airportInfoIntent   flight status {AIRPORTCODE}
airportInfoIntent   airport status {AIRPORTCODE}
airportInfoIntent    info {AIRPORTCODE}
airportInfoIntent   flight info {AIRPORTCODE}
airportInfoIntent   airport info {AIRPORTCODE}
airportInfoIntent    delay info {AIRPORTCODE}
airportInfoIntent   flight delay info {AIRPORTCODE}
airportInfoIntent   airport delay info {AIRPORTCODE}
airportInfoIntent    status info {AIRPORTCODE}
airportInfoIntent   flight status info {AIRPORTCODE}
airportInfoIntent   airport status info {AIRPORTCODE}
airportInfoIntent    for {AIRPORTCODE}
airportInfoIntent   flight for {AIRPORTCODE}
airportInfoIntent   airport for {AIRPORTCODE}
airportInfoIntent    delay for {AIRPORTCODE}
airportInfoIntent   flight delay for {AIRPORTCODE}
airportInfoIntent   airport delay for {AIRPORTCODE}
airportInfoIntent    status for {AIRPORTCODE}
airportInfoIntent   flight status for {AIRPORTCODE}
airportInfoIntent   airport status for {AIRPORTCODE}
airportInfoIntent    info for {AIRPORTCODE}
airportInfoIntent   flight info for {AIRPORTCODE}
airportInfoIntent   airport info for {AIRPORTCODE}
airportInfoIntent    delay info for {AIRPORTCODE}
airportInfoIntent   flight delay info for {AIRPORTCODE}
airportInfoIntent   airport delay info for {AIRPORTCODE}
airportInfoIntent    status info for {AIRPORTCODE}
airportInfoIntent   flight status info for {AIRPORTCODE}
airportInfoIntent   airport status info for {AIRPORTCODE}

Updated skill utterances

Click on the “Save” button to finish updating the skill configuration.

Testing Account Linking

To test the account linking experience for a new Airport Info user, go to the Alexa app and search for Airport Info (or whatever your skill is named). The skill should be currently enabled, so you must temporarily disable it by clicking “Disable” and then the “Disable Skill” button. If you are unable to find your skill, make sure that you have enable testing for it, in the “Test” panel of the Skill’s Configuration Page.

Disabling an Alexa skill

If you now click on “Enable”, you should be redirected to the Twitter login page. After authenticating, you will be asked if you want to authorize Airport Info to use your Twitter account. Click on the “Authorize app” button.

Twitter authorization of an Alexa skill

After authorizing the app, you will be redirected to the skill page and you should see a success message confirming the account linking.

In the event that you did not receive a successful link card, there are a number of things you can validate to determine the root cause of the issue. First, make sure all information in your skill’s Account Linking configuration page is properly set, and that the Authorization URL is properly encoded. Next, make sure your CONSUMER_KEY and CONSUMER_SECRET values are accurately pasted in the Twitter Helper Object you created earlier. Lastly, you can check your CloudWatch Logs for any recent errors reported from your AWS Lambda service. Check out the Amazon docs on accessing CloudWatch logs if you need more info.

Account linking success

Testing the Twitter-Enabled Skill

At this point, you should have your updated skill available on your Alexa-enabled device. Ask Alexa to tweet the status for ATL by saying, “Alexa, ask Airport Info to tweet flight status for ATL”.

If everything is correctly linked, you should see a tweet on your Twitter account.

Successful Alexa skill tweet

What’s Next?

In this series on building an Alexa skill, we’ve covered a lot of ground, from setting up a local development environment to submitting a skill for certification and expanding its power by linking it to other accounts. We have locally tested a skill to determine whether it behaves as expected, and also tested it in the service simulator on the Developer Console. We have even deployed it to an Alexa-enabled device. We also have gone over how to implement persistence in a skill so that users will be able to access saved information. In this final blog in the series, we’ve discussed how to link a skill to accounts for other services and make it even more useful. Let us know what you’ve built in the comments!

Juan Pablo Claude

Author Big Nerd Ranch

During his tenure at BNR, Juan Pablo has taught bootcamps on macOS development, iOS development, Python, and Django. He has also participated in consulting projects in those areas. Juan Pablo is currently a Director of Technology focusing mainly on managing engineers and his interests include Machine Learning and Data Science.

Speak with a nerd

Schedule a call today! Our team of nerds are ready to help

Let's Talk

Related Posts

We are ready to discuss your needs.

Stay in Touch WITH Big Nerd Ranch News