Account Linking for Alexa skills

linking up alexa users with users in your system

Tom Berwick
Alexa Skills Dev
7 min readNov 12, 2020

--

Photo by Chris Liverani on Unsplash

I’ve been asked to approach the subject of account linking within Alexa skills so this will be my attempt.

Whilst this will involve a bit of server-side code to implement an “authentication service” to interact with, it should be noted that this isn’t the main focus of the tutorial. So any “authentication service” we use here is purely for demo purposes and won’t follow some of the best practice/security around that side of things. It’s purely to show how Alexa might link up with your service.

First off the official Alexa docs can be found here if you’re interested and there are two ways to initiate account linking. Either via the Alexa app in response to someone using your skill (which is the most common), or you can prompt them to link via your app/website as a pro-active measure. We will cover linking via the Alexa app in this tutorial.

The Firebase Authentication Service

So first up we’re going to develop our authentication service. As I say this will be quite “dumb” and you’ll definitely want to look at best practice and improving this for your own production skills. In the interest of brevity I will skip over a lot of this, but I will try to highlight the important parts for you.

You will need to setup a firebase project at https://console.firebase.com

Then create a folder on your local machine to store your code in and follow the instructions within the firebase console to configure firebase hosting, functions and firestore (tip make sure you create a firestore database in the console before running firebase init on the command line).

Sample code can be found at the following github repo. https://github.com/TommyBs/alexa_account_linking_firbase_example

The important bits are the following lines if the user is signed in: (this can be located in public/index.html)

if(user) {
const uid = user.uid;
const code = uuidv4(); //Please note this is not a bit in function but defined elsewhere
const urlParams = new URLSearchParams(window.location.search);
const state = urlParams.get(“state”);
const redirectURI = urlParams.get(“redirect_uri”);
const url = redirectURI + “?state=” + state + “&code=” + code;
db.collection(‘codes’).doc(uid).set(
{code:code,
uid:uid,
created_at: firebase.firestore.FieldValue.serverTimestamp()},{merge:true});
// Redirect to url
window.location.href = url;
}

Effectively what we’re doing here is generating a new random code as a bit of added security and storing that in a database against our current user along with a current timestamp. We then redirect to a new url that is provided by the Alexa service and pass along a state variable supplied by Alexa and our code. This is to ensure the authenticity of the request. We will also use the timestamp to make sure that the access token is generated within a reasonable amount of time of the code being generated.

You also need to set some appropriate rules for your firestore implementation in the firestore console or your firestore.rules file:

match /codes/{uid} {
allow read, write: if request.auth != null;
}

The backend/server side/generating tokens

We now need a way to actually generate our access and refresh tokens and validate them. As firebase doesn’t support persistent refresh tokens and Alexa recommends that you do to make the account linking process as smooth as possible, we need to create our own tokens. The code for this can be found in functions/index.js. The entry point to this file is the exports.access_token line located at the bottom of the file. Essentially we make use of the firebase admin sdk to create a custom token https://firebase.google.com/docs/auth/admin/create-custom-tokens

Alexa actually supports authorization_code or implicit grant types ( https://developer.amazon.com/en-US/docs/alexa/account-linking/requirements-account-linking.html ), but for the purposes of our tutorial we’re going to focus on the authorization code.

When Alexa makes a request to the authorization server with the access token or refresh token it will be one of two grant types which will be passed as a grant_type parameter in a POST request. These will either be authorization_code or refresh_token so we need to handle each case differently.

Essentially though for both cases, provided we have a user with the received code or refresh token we generate a new access token for them. Note that we don’t regenerate the refresh token in a refresh token request. This is because the Alexa docs specifically call out not to invalidate a refresh token at this point! https://developer.amazon.com/en-US/docs/alexa/account-linking/requirements-account-linking.html#access-token-uri-requirements

Once you’re happy with the code, simply run firebase deploy from your project directory.

Finally make sure your google cloud project has the following API enabled :

IAM Service Account Credentials API ( https://console.developers.google.com/apis/api/iamcredentials.googleapis.com/overview )

and following the docs here https://firebase.google.com/docs/auth/admin/create-custom-tokens#service_account_does_not_have_required_permissions to grant the appropriate permissions to your firebase service account.

Configuring Alexa to make use of our authorization service

Now, before we being in earnest with the Alexa side I think I would like to point out the best practices https://developer.amazon.com/en-US/docs/alexa/account-linking/account-linking-best-practices.html

I’m not doing some of these such as branding etc. for this tutorial in the interest of keeping things simple. But for things like that you would need to edit your index.html file from the previous section. I’m also building this as a custom Alexa Hosted skill starting from scratch rather than a template.

Before we get to the code we first need to configure the skill to use account linking. To do this, select your skill in the Alexa Developer console and under “tools” on the left-hand-side choose “account linking”.

For the purposes of this tutorial I’ve enabled
“Allow users to link their account to your skill from within your application or website”

For “Your Web Authorization URI” use the web address of your firebase hosting project.

For “Access Token URI” use the web address of your cloud function.

Create a client Id and a client Secret (ideally you should check these in the request to your token end point to make sure the request is coming from Alexa). And for “Your Redirect URLs” just add the previous URL for the firebase hosting site.

Then hit save.

The code

Within the code editor add firebase to your package.json file. Then back within the firebase console navigate to “Settings” -> “General” and scroll to the bottom until you see “Firebase SDK Snippet” and select the “Config” radio button. Copy the code here and paste it into your index.js file within the Alexa code editor as well as “requiring” the firebase library. Then initialize your firebase app. It should look something like below:

const firebase = require(‘firebase’);
const firebaseConfig = {
apiKey: “apiKey”,
authDomain: “firebaseapp.domain”,
databaseURL: “databaseurl",
projectId: “projectid”,
storageBucket: “bucket”,
messagingSenderId: “senderId”,
appId: “appId”
};
firebase.initializeApp(firebaseConfig);

Next you need to decide what handlers will require account linking. Amazon recommend that not all handlers should require it, so that users can interact with part of your skill without an account — that might not always be possible depending on your skill however. You can find the docs for what I’m about to go through here https://developer.amazon.com/en-US/docs/alexa/account-linking/add-account-linking-logic-custom-skill.html

For the sake of this tutorial we will check in the LaunchRequestHandler . If a user has already linked their account to your Alexa skill, it will appear as an accessToken field under the user key of the request. Don’t confuse this with the apiAccessToken field which is used to make device API calls. You can access it like so:

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

You now need to do two things. First check that the access token actually exists e.g if (accessToken) and if it does you then need to check it’s authentic by signing into firebase https://firebase.google.com/docs/auth/admin/create-custom-tokens#sign_in_using_custom_tokens_on_clients

await firebase.auth().signInWithCustomToken(accessToken).catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// ...
});

If either of these things are false or throw an error, you need to let the user know to link their account. With the Alexa SDK this is really simple, you just need to chain a call to withLinkAccountCard to your response e.g:

let speakOutput = 'Please link your account';
if(!accessToken) {
return responseBuilder.speak(speakOutput).withLinkAccountCard().getResponse()
}

You might want to point out to your users that to do this they need to navigate to the activity section of the Alexa app which is under “more”->”activity”.

If both those conditions pass, you can grab the current user from the firebase context and then access info from that user or query your database etc.

let user = firebase.auth().currentUser;
if (user) {
//grab some info from your db and return it
//e.g
responseBuilder.speak("Welcome bob, you have foo in the database").getResponse();
}

Alexa should handle refreshing the access token for you!

So your full LauchRequestHandler (and requires) should look like this:

const firebase = require('firebase');const firebaseConfig = {
apiKey: "...",
authDomain: "...",
databaseURL: "...",
projectId: "...",
storageBucket: "...",
messagingSenderId: "...",
appId: "..."
};
firebase.initializeApp(firebaseConfig);
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.NONE)
const LaunchRequestHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'LaunchRequest';
},
async handle(handlerInput) {
const {responseBuilder} = handlerInput;
let speakOutput = 'Please link your account to continue';
const accessToken = handlerInput.requestEnvelope.context.System.user.accessToken;

if(!accessToken) {
return responseBuilder.speak(speakOutput).withLinkAccountCard().getResponse()
}

await firebase.auth().signInWithCustomToken(accessToken).catch(function(error) {

var errorCode = error.code;
var errorMessage = error.message;
return responseBuilder.speak(speakOutput + errorMessage).withLinkAccountCard().getResponse()
});
let user = firebase.auth().currentUser;
if (user) {
// login successful
speakOutput = `Welcome ${user.displayName}!`;
} else {
// No user is signed in.
speakOutput = `Welcome, please link your account!`;
}
return responseBuilder
.speak(speakOutput)
.reprompt(speakOutput)
.getResponse();
}
};

One big thing to note is the line:

firebase.auth().setPersistence(firebase.auth.Auth.Persistence.NONE)

We need to set firebase persistence to .NONE otherwise it timeouts our alexa skill code.

So go ahead and enable your skill, link your account, then talk to your skill again at it should say your name!

Any issues with the above then please leave a comment below and I’ll try my best to help or update my code. Most of the issues I faced were actually down to typos and incorrect IAM permissions, but the typos should all be fixed in the github repo which can be found here https://github.com/TommyBs/alexa_account_linking_firbase_example

This tutorial was a request from my facebook group so feel free to join it and let me know if there’s any other tutorials you would like to see https://www.facebook.com/groups/alexaskillsdevelopers

--

--

Tom Berwick
Alexa Skills Dev

Mobile App, game and full stack developer. Constantly trying to learn new things and dabble in growth hacking