Building Secure Alexa Skills - Part III
Dec 27th, 2023 update: Webtasks have been deprecated.
As of June 2022, some of these libraries are outdated/deprectaed (e.g.
request
)
A reader of this blog, tjaffri, raised two questions:
refresh_tokens
did not work with Alexa (and Auth0).- How to generate
access_tokens
for testing.
Refreshing access_tokens in Alexa
In my original prototype, I did not configure Alexa to request refresh_tokens
so I didn’t know for sure, but I assumed it would since all this is based on “standards” (OpenID Connect/OAuth2).
Naturally, it failed. The reason it failed was that Auth0 was not returning the expires_in
attribute in the refresh response body.
The token exchange endpoint is used for both code
and refresh
requests:
The difference between the two is is just the grant_type
parameter in the body:
POST
https://{your tenant}.auth0.com/oauth/token
content-type: application/json
{
"grant_type": "refresh_token",
"client_id": "{CLIENT_ID}",
"client_secret": "{CLIENT_SECRET}",
"refresh_token": "{refresh_token}"
}
In a code
exchange request, the response looks like this:
{
"access_token": "{THE ACCESS TOKEN}",
"id_token": "{THE ID TOKEN}",
"expires_in": 120
}
Alexa uses the expires_in
value to decide whether a new token is required or not.
Notice that the
access_token
is generally an opaque thing. Only to be consumed and interpreted by the API. The fact that in Auth0’s case it is actually a JWT (and therefore “decodable”), is not relevant. Alexa doesn’t know how to extract that information. Thus theexpires_in
parameter.
The reason it didn’t work is simple: a bug. Auth0 omitted the expires_in
in the refresh response, and that broke Alexa (not entirely though, it just didn’t refresh the token).
expires_in
is technically RECOMMENDED not required. But it is required in Alexa. This of course is not super clear in the documentation.
A fix is now ready and the flow will work as expected once it is live sometime next week.
Reminder:
refresh_token
is sent by Auth0 when you add theoffline_access
scope to the authorization request.
What to do in the meantime?
There’s a workaround you can try if you cannot wait until the fix is in production. You can proxy the token exchange request. And here comes Webtask for the rescue:
The code would look like this:
var Webtask = require('webtask-tools');
var request = require('request');
var app = new (require('express'))();
app.post('/token', function(req, res) {
console.log(req.webtaskContext.data);
var grant_type = req.webtaskContext.data.grant_type;
var client_id = req.webtaskContext.data.client_id;
var client_secret = req.webtaskContext.data.client_secret;
var redirect_uri = req.webtaskContext.data.redirect_uri;
var options = {
method: 'POST',
url: 'https://{YOUR AUTH0 ACCOUNT}.auth0.com/oauth/token',
json: {
'grant_type': grant_type,
'client_id': client_id,
'client_secret': client_secret,
'code': code,
'redirect_uri': redirect_uri
}
};
if(grant_type == 'authorization_code'){
options.json.code = req.webtaskContext.data.code;
} else {
if(grant_type == 'refresh_token')
{
options.json.refresh_token=req.webtaskContext.data.refresh_token;
}
}
request(options, function(err, res, body) {
console.log('Error', err);
if(err) return res.sendStatus(500);
if(options.json.refresh_token){
//Add the missing expires_in for refresh token flow
body.expires_in = 120;
res.json(body);
}
});
});
module.exports = Webtask.fromExpress(app);
Then in your Alexa configuration, you use the Webtask URL as the token exchange endpoint.
Getting Test access_tokens for your API
This is an easy one. You just create a new Client (e.g. your test runner). Then you can use client credentials
flow to obtain a token.
- Register a new Client in Auth0 (you can specify “Non Interactive Client”).
- Then on the API section, click on Clients and authorize it to request tokens with the scopes you need.
- Use the
client credentials
flow to request a token for this API (with the given scopes).
curl --request POST \
--url https://{YOUR AUTH0 ACCOUNT}.auth0.com/oauth/token \
--header 'content-type: application/json' \
--data '{"client_id":"{THE TEST CLIENT ID}","client_secret":"{THE TEST CLIENT SECRET}","audience":"https://whatshouldiwear","grant_type":"client_credentials"}'
Auth0 will send you back an access_token
you can use with your test runner. When you are done, you can simply delete the Client. No changes will happen on the API or anything.
Merry Christmas tjaffri!