Validate and Use Access Tokens in Custom Skill Code


Once a user successfully enables your skill and links their Alexa account with the other service, requests sent to your skill include the user's access token (accessToken). Add logic to the service for your skill to verify and use this access token.

This document covers account linking logic for custom skills. If you are working on a smart home or video skill, see Validate and Use Access Tokens in Smart Home and Video Skill Code.

Decide which requests require account linking

A custom skill can require authentication (and therefore a valid access token) on all requests, or on just some requests. This depends on your skill's design and functionality.

For example, the Ride Hailer skill might require user authentication to order a car, but does not require authentication to just ask whether the service is available in a particular city. Therefore, the code for this skill might do the following:

  • The handler for the OrderCar intent checks for a valid accessToken and uses that token to retrieve the user's Ride Hailer profile and order a car.
  • The handler for the RideHailerAvailabilityByCity intent looks up public information on the Ride Hailer service and returns a response. This handler does not need to check for an accessToken.
  • The handler for the LaunchRequest provides an intro to the skill and asks the user what they want to do. This handler does not need to check for an accessToken.

For requests that require authentication, do the following:

  1. Get the access token from the request
  2. Validate the token and authenticate the user
  3. Return a link account card and user-friendly output speech if the token is not present or is invalid.

For intents that do not require authentication, you can skip these steps and just provide a normal response.

Required account linking

Some skills have no alternate functionality to offer if the user has not linked their account. In this case, every request needs to check for the access token and return an appropriate prompt and link account card if the user has not linked their account:

User: Alexa, open Ride Hailer.

Alexa: Welcome to Ride Hailer. To use this skill, you must have a Ride Hailer account. I've sent some information to the Alexa app. Open your Alexa app and click on the link to connect your Ride Hailer account with Alexa.
Session ends.

If all of your requests require account linking, be sure turn off the Allow users to enable skill without account linking option.

Optional account linking

If your skill includes meaningful functionality that does not require the linked account, turn on the Allow users to enable skill without account linking option. This lets users skip the account linking flow when they enable the skill, which can reduce user friction and encourage users to try your skill. See Let Users Enable Your Skill without Account Linking.

One way to encourage users to link their accounts is to explain the benefits of connecting to the user account and ask the user if they want to proceed. The welcome message you return in your LaunchRequest is a good place to do this. For example:

User: Alexa, open Ride Hailer.

Alexa: Welcome to Ride Hailer. If you have a Ride Hailer account, I can order you a ride. Do you want to set that up now?
User: Yes. (This sends the built-in AMAZON.YesIntent)

The skill sends the link account card, as discussed in Respond to the User if the Token is Invalid or Missing
Alexa: OK, I've sent some information to the Alexa app. Open your Alexa app and click on the link to connect your Ride Hailer account with Alexa.
Session ends.

If the user responds with "no," your skill can offer alternate functionality that does not require an account:

User: Alexa, open Ride Hailer

Alexa: Welcome to Ride Hailer. If you have a Ride Hailer account, I can order you a ride. Do you want to set that up now?
User: No.

Alexa: OK, without a Ride Hailer account, I can give you driving directions and traffic reports. I can also tell you the cities where our service is available. You can ask for a specific city, or ask for our most recently added cities. Which do you want?
Conversation with the user continues…

Get the access token from the request

If the user has successfully linked their account, the user's access token (accessToken) is included in each request sent to your skill as long as their accessToken is valid. If they have not yet linked their account, the accessToken property is not included in the request.

If you configured the authorization code grant flow and the Alexa service is unable to obtain a new pair of tokens from your authorization server due to an invalid or expired refresh token for a user who is already linked, the accessToken property isn't included in the request to your skill. In this scenario, the Alexa service deletes the existing invalid or expired tokens and sends a push notification and a re-linking home card to the user's Alexa app to have them link their account back.

The token is available in the accessToken property of the user object, which is available in the context object. The context object is included in all requests. You can access the accessToken in context.System.user.accessToken.

In this example, the user's accessToken is "Atza|<accessToken>". Note that a real access token is typically much longer than this. Some objects within the request are not shown for brevity:

{
  "version": "1.0",
  "session": {},
  "context": {
    "AudioPlayer": {
      "playerActivity": "IDLE"
    },
    "Display": {},
    "System": {
      "application": {
        "applicationId": "amzn1.ask.skill.1111111-2222-3333-4444-5555555"
      },
      "user": {
        "userId": "amzn1.ask.account.XXXXXXXXXX",
        "accessToken": "Atza|<accessToken>"
      },
      "device": {},
      "apiEndpoint": "https://api.amazonalexa.com",
      "apiAccessToken": "<apiToken>"
    }
  },
  "request": {}
}

Do not confuse the accessToken with the apiAccessToken included on all requests. The apiAccessToken is used for Alexa Skills Kit APIs, such as the Device Settings API and Progressive Response API.

This example shows how you access the accessToken in your request handler.

Copied to clipboard.

This code example uses the Alexa Skills Kit SDK for Node.js (v2).

const OrderCarIntentHandler = {

    //...

    handle(handlerInput){
        // This intent requires an access token so that we can get the user's
        // Ride Hailer user profile with payment information.

        // The access token is in the Context object. Access the
        // request in the HandlerInput object passed to the
        // handler.

        var accessToken = handlerInput.requestEnvelope.context.System.user.accessToken;

        // ...
    }
};

Copied to clipboard.

This code example uses the Alexa Skills Kit SDK for Java.

public class OrderCarIntentHandler implements RequestHandler {
    // ...

    @Override
    public Optional<Response> handle(HandlerInput handlerInput) {

        // This intent requires an access token so that we can get the user's
        // Ride Hailer user profile with payment information.

        // The access token is in the Context object. Access the
        // request in the HandlerInput object passed to the
        // handler.

        String accessToken = handlerInput
                .getRequestEnvelope()
                .getContext()
                .getSystem()
                .getUser()
                .getAccessToken();

        // ...handler continues
    }
}

Validate the access token

For a request that requires authentication, your code should do at least two checks:

  1. Verify that the accessToken exists.
  2. Verify that the token represents a valid user in your resource server.

Verify that the token exists

Make sure that the accessToken exists. If the accessToken property is not included in the request, this means that the user has not yet successfully linked their account or that the tokens for the user's linked account are expired or invalid.

In the Alexa Skills Kit SDK for Node.js, the accessToken property on the user (for example, handlerInput.requestEnvelope.context.System.user.accessToken) returns undefined if the token does not exist. In the Alexa Skills Kit SDK v2 for Java, User.getAccessToken() returns null if the token does not exist.

Verify that the token is valid

If the accessToken exists, verify that it identifies a user in your resource server. The token could become invalid for multiple reasons, for example:

  • The user deleted or canceled their account with your service. For example, an Alexa user might have set up account linking with Ride Hailer, then later canceled their Ride Hailer account. At this point, the token stored by the Alexa service would identify a non-existent user.
  • The token has expired, and the Alexa service was unable to obtain a new token. This can occur with an authorization code grant if your authorization server does not provide refresh tokens. This can also occur if you use an implicit grant, which does not support refresh tokens.

If the token is valid, handle the request normally. You can use the token to access data from your resource server as needed. In the Ride Hailer example, the skill would retrieve profile and payment information for the user from the Ride Hailer service, order a car, and return a confirmation to the user. For details, see Return a Response.

Respond to the user if the token is invalid or missing

If the access token does not exist or is invalid on a request that requires authentication, return a response containing the following:

  • Output speech text explaining to the user that they need to link their account to use this feature. Succinctly explain the benefit of account linking in the speech text.
  • A link account card. This is a special type of card that tells the user to link their account. When displayed in the Alexa app, this card displays a link to your authorization URI. The user can start the account linking process right from this card.

In your JSON response include the card property with the type set to LinkAccount:

{
    "version": "1.0",
    "sessionAttributes": {},
    "response": {
      "outputSpeech": {
        "type": "PlainText",
        "text": "You must have a Ride Hailer account to order a car. Please use the Alexa app to link your Amazon account with your Ride Hailer Account."
      },
      "card": {
        "type": "LinkAccount"
      },
      "shouldEndSession": true
    }
}

Any additional card properties, such as cardTitle, are ignored.

The following examples show how to return a link account card with either the Alexa Skills Kit SDK for Node.js or the Alexa Skills Kit SDK v2 for Java.

Copied to clipboard.

This code example uses the Alexa Skills Kit SDK for Node.js (v2).

const OrderCarIntentHandler = {

  // ...

  handle(handlerInput){

    // This intent requires an access token so that we can get the user's
    // Ride Hailer user profile with payment information.

    // The access token is in the Context object. Access the
    // request in the HandlerInput object passed to the
    // handler.

    var accessToken = handlerInput.requestEnvelope.context.System.user.accessToken;

    if (accessToken == undefined){
        // The request did not include a token, so tell the user to link
        // accounts and return a LinkAccount card
        var speechText = "You must have a Ride Hailer account to order a car. " +
                    "Please use the Alexa app to link your Amazon account " +
                    "with your Ride Hailer Account.";

        return handlerInput.responseBuilder
            .speak(speechText)
            .withLinkAccountCard()
            .getResponse();
    } else {

        // Use the token to access the user's profile. This should also verify that the
        // token represents a valid Ride Hailer user.

        // ...

    }
  }
};


Copied to clipboard.

This code example uses the Alexa Skills Kit SDK for Java.

public class OrderCarIntentHandler implements RequestHandler {

    // ...

    @Override
    public Optional<Response> handle(HandlerInput handlerInput) {

        // This intent requires an access token so that we can get the user's
        // Ride Hailer user profile with payment information.

        // The access token is in the Context object. Access the
        // request in the HandlerInput object passed to the
        // handler.

        String accessToken = handlerInput
                .getRequestEnvelope()
                .getContext()
                .getSystem()
                .getUser()
                .getAccessToken();

        if (accessToken != null) {
            // Call a method to validate the token, get the user's Ride Hailer
            // profile and order a ride
            return orderCarForUser(accessToken, handlerInput);
        } else {
            // The request did not include a token, so tell the user to link
            // accounts and return a LinkAccount card

            String speechText = "You must have a Ride Hailer account to order a car. "
                    + "Please use the Alexa app to link your Amazon account "
                    + "with your Ride Hailer Account.";

            // Build a response with output speech and a LinkAccount card.
            return handlerInput.getResponseBuilder()
                    .withSpeech(speechText)
                    .withLinkAccountCard()
                    .build();
        }
    }
}

In most cases, this response should end the session, since the user cannot continue with their request until after they have linked their account. For a better user experience, save any relevant user data in a persistent data store so that the user can pick up where they left off after they link the account and return to the skill.

If your skill includes some intents that don't require authentication, it may make sense to ask the user a different question and keep the session open instead.


Was this page helpful?

Last updated: Sep 10, 2024