How can users grant permission for my website to manage their Amazon Alexa Lists
Asked Answered
S

3

3

I want users of my Next.js TypeScript app to grant it permission to manage their Alexa Lists.

I figured this would be possible with OAuth2.

I figured I'd need to create a button in my website that takes the user to an Amazon URL that allows the user to grant my website permission to manage their Alexa lists (and then generates a code that it includes in a GET request that happens as a redirect to a "callback" URL that I registered as the redirect_uri when setting up OAuth2 in Amazon).

I figured the button would be a link to a URL defined like

const url = `${oauth2BaseUrl}?client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodeURIComponent(redirectUrl)}&response_type=code&scope=${scope}`;

This is generally how OAuth2 works, in my experience.

But I've found Amazon's docs incredibly unhelpful.

I see permissions / scopes mentioned here called alexa::household:lists:read alexa::household:lists:write.

I've set up my API endpoint (which I'll specify at redirectUrl) to exchange the Amazon authorization code for an Amazon access token following the code examples shown there.

I've set oauth2BaseUrl to be 'https://www.amazon.com/ap/oa' (found at https://developer.amazon.com/docs/login-with-amazon/authorization-code-grant.html).

For client ID, I'm using the one for my Alexa skill that I created. Is that correct?

I'm using Next-auth, but I'd be curious if there are any other libraries that could make any of this easier.

Here are permissions I've added in my Skill:

enter image description here

I always get:

400 Bad Request
An unknown scope was requested

But if I just use scopes these different scopes instead, I see it behave how I'd expect (but I lack List permissions): alexa::skills:account_linking postal_code profile:user_id.

P.S. I also started setting up Login With Amazon, but I don't understand why that would be necessary. I'm not looking to offer a federated login feature.

Submerge answered 8/4, 2023 at 22:34 Comment(3)
To be able to manage users' Alexa Lists, you need to use the LWA, which is separate from the Alexa Skills Kit (ASK). The client ID you're using should be from LWA, not your Alexa Skill.Resonance
@Resonance Thanks for your comment! I've now set up my OAuth to use the LWA client ID. What other steps are necessary to achieve my goal? Thanks.Submerge
Added an answer hope that helps.Resonance
S
1

6 months after asking the question, I finally figured it out.

Given the meager Amazon docs, I was starting to wonder if this was even possible.

// Revoke permissions: https://www.amazon.com/ap/adam https://www.amazon.com/gp/help/customer/display.html%3FnodeId%3DGSNRZP4F7NYG36N6

import Alexa from 'ask-sdk-core';
import * as Model from 'ask-sdk-model';
import { type Profile, type TokenSet } from 'next-auth';
import { type OAuthConfig } from 'next-auth/providers';

import { baseUrl } from '../../config/config';

// From https://developer.amazon.com/settings/console/securityprofile/web-settings/update.html:
const clientId = String(process.env.NEXT_PUBLIC_LWA_CLIENT_ID);
const clientSecret = String(process.env.LWA_CLIENT_SECRET);
const oauth2BaseUrl = 'https://www.amazon.com/ap/oa';
const tokenEndpoint = 'https://api.amazon.com/auth/o2/token';
const userInfoUrl = 'https://api.amazon.com/user/profile';
const amazonAlexaApiEndpoint = 'https://api.amazonalexa.com';

const { services } = Model;
const { LwaServiceClient } = services;

const id = 'alexa';

const authorization = {
  params: {
    redirect_uri: `${baseUrl}/api/auth/callback/${id}`,
    response_type: 'code',

    scope: 'profile profile:user_id postal_code', // https://developer.amazon.com/docs/login-with-amazon/customer-profile.html
  },
  url: oauth2BaseUrl,
};

console.log({ authorization });

/**
 * https://next-auth.js.org/configuration/providers/oauth#userinfo-option
 */
const userinfo: OAuthConfig<Profile>['userinfo'] = {
  // The result of this method will be the input to the `profile` callback.
  async request(context) {
    const accessToken = String(context.tokens.access_token);
    const input = {
      headers: {
        Accept: 'application/json',
        Authorization: `Bearer ${accessToken}`,
      },
      method: 'GET',
    };
    console.log({ input, userInfoUrl });
    const response = await fetch(userInfoUrl, input); // https://developer.amazon.com/docs/login-with-amazon/obtain-customer-profile.html#call-profile-endpoint
    const json = await response.json();
    const alexaUserId = json?.user_id;
    const profile: Profile = {
      id: alexaUserId,
      ...json,
    };
    console.log({ alexaUserId, json, profile });

    return profile;
  },
};

export const alexaProvider: OAuthConfig<Profile> = {
  authorization,
  clientId,
  clientSecret,
  id,
  idToken: false,
  name: 'Alexa',
  profile(profile: Profile, tokenSet: TokenSet) {
    console.log({ profile, tokenSet });
    profile.tokenSet = tokenSet;
    return profile;
  },
  token: {
    // https://next-auth.js.org/configuration/providers/oauth#token-option
    params: {
      grant_type: 'authorization_code',
    },
    url: tokenEndpoint,
  },
  type: 'oauth' as const,
  userinfo,
};

export async function getAccessTokenForScope(scope: string) {
  const apiConfiguration = {
    apiClient: new Alexa.DefaultApiClient(),
    apiEndpoint: amazonAlexaApiEndpoint,
    authorizationValue: '',
  };
  const authenticationConfiguration = {
    clientId,
    clientSecret,
  };

  const lwaServiceClient = new LwaServiceClient({ apiConfiguration, authenticationConfiguration });
  const accessToken = await lwaServiceClient.getAccessTokenForScope(scope);
  return accessToken;
}

The insights:

  1. Using Login With Amazon (LWA) is probably required.
  2. As you can see, there are 4 different Amazon endpoints specified above.
  3. No scopes starting with alexa:: exist anywhere within my repo. Those scopes only belong on the AWS sites below.
  4. My Alexa Client ID and Secret do not exist anywhere within my repo. Those only get used on the AWS sites below.
  5. There are 2 allow-lists that you need to add your redirect URL to. One at https://developer.amazon.com/settings/console/securityprofile/web-settings/view.html?identityAppFamilyId= and one at https://developer.amazon.com/alexa/console/ask/amzn1.ask.skill....
  6. After you set up your Security Profile, go to https://developer.amazon.com/loginwithamazon/console/site/lwa/overview.html (Login With Amazon) to create a new "Login with Amazon Configuration" linking to that.
  7. See important AWS dev console settings below.

Alexa settings Alexa permissions

Submerge answered 26/10, 2023 at 11:53 Comment(2)
I have implemented a complete app-to-app account linking flow, however at present users must manually grant the two list permissions (read and write) - were you able to get it working so the list permission is granted as part of the app-to-app linking flow as I'm stuck here :(Rubrician
@Rubrician I was surprised that my flow doesn't explicitly ask for permission about Alexa lists, yet they seem to work. See the flow at smarthomelist.com I'll be curious for your feedback.Submerge
M
2

I got your ping from another thread. I haven't used App-to-App account linking, so I don't have a complete answer for you. But to my knowledge, alexa::household:lists:read and alexa::household:lists:write are skill permissions (not OAuth scope). If enabled, the Alexa app would prompt for the user's consent after the OAuth authentication flow.

For example, here is the basic account linking flow (not App-to-App account linking) with the Alexa app on Android:

  1. Open the Alexa app
  2. Select my skill > Enable
  3. The Alexa app opens Login with Amazon page in Chrome (I used LWA as my OAuth provider)
  4. I login and authorize the request
  5. It returns to the Alexa app, with the message "Your skill account has been successfully linked" > click Close
  6. The Alexa app then prompts for Account Permissions. That's the step where I can grant the skill access to my Alexa lists

So granting the Alexa lists permission is not part of the OAuth in the basic account linking flow.

As an experiment, I tried adding alexa::household:lists:read as a scope in my skill's account linking configuration, and it broke account linking -- Alexa app would display an error message instead of opening the LWA page. So I don't think they are OAuth scopes. It would also explain why you were getting 400 Bad Request -- An unknown scope was requested error.

As for your scenario, are you looking to implement App-to-App account linking with the Alexa app flow or the LWA fallback flow? If it's the latter, I suspect this may not be a supported use case based on my observation above. I would suggest reaching out to Amazon developer support to confirm.

Maxia answered 16/4, 2023 at 2:29 Comment(11)
Thanks for responding! The flow I want is: "Alexa app only (browser flow) – Users accomplish account linking entirely within the Alexa app. This is the most common flow." I've tried posting in the AWS forum but haven't gotten any response.Submerge
@Submerge Just to clarify, the UX I described above is actually the "Alexa app only (browser flow)". You mentioned creating a button on your website that takes the user to an Amazon URL that allows them to grant your website permission to manage their Alexa lists -- that would be considered as an app-to-app linking flow, where the user starts from your app/website instead of the Alexa App.Maxia
Can you confirm if you want the Alexa-app only flow? If so, can you clarify what the issue is? The Alexa app should prompt for the Alexa list permissions in step 6 (after completing OAuth).Maxia
I apologize; you are right. I've been so confused by this documentation. I want the one that says "Starting from your app – In this flow, the user starts from your app or website, chooses to initiate account linking, and is then redirected to the Alexa app (or Login with Amazon, if the Alexa app is not installed). The user acknowledges the account linking request within the Alexa app (or Login with Amazon), and is then redirected back to your app or website, which completes account linking and enables the skill by using APIs provided by Alexa. For details about this flow, see ..."Submerge
https://mcmap.net/q/752231/-how-do-i-use-login-with-amazon-to-link-a-user-account-to-my-skill looks interesting but unclear. I also found const lwaServiceClient = new LwaServiceClient({ apiConfiguration, authenticationConfiguration }); const accessToken = await lwaServiceClient.getAccessTokenForScope(scope); but am not sure when to call it and what authorizationValue means and what value to assign to it (required property of apiConfiguration).Submerge
developer.amazon.com/en-US/docs/alexa/custom-skills/… looks important. It says "Your skill receives authorization codes at the reciprocalAccessTokenUrl that you specify in the account linking schema within the skill manifest." But it links to developer.amazon.com/en-US/docs/alexa/smapi/… which says "In the developer console, you specify these on the Build > Account Linking page using the Your Redirect URLs field." which I've done. 🤷Submerge
No worries :) FYI that this answer you referenced is for the Alexa-app only flow with LWA. I have the same setup for my skill. Based on this post, I'm afraid your desired workflow is not a supported use case.Maxia
I would suggest posting here or submit a Contact Us if you want an official confirmation. I'm not sure where you posted in the AWS forum, but Alexa is not part of AWS so you may not get a response there.Maxia
Thanks! I haven't gotten any answer from 4 days ago: amazon.developer.forums.answerhub.com/questions/248106/…Submerge
6 months later but I finally figured out how to get my Next.js app to connect to my Amazon Alexa lists: https://mcmap.net/q/743346/-how-can-users-grant-permission-for-my-website-to-manage-their-amazon-alexa-listsSubmerge
Glad to hear you figured it out :) I'm surprised that it's working for you after adding alexa::household:lists:read and alexa::household:lists:write as OAuth scope, as they broke my account linking flow. It would be helpful if you can update your write-up and describe the actual user experience with your setup. ThanksMaxia
S
1

6 months after asking the question, I finally figured it out.

Given the meager Amazon docs, I was starting to wonder if this was even possible.

// Revoke permissions: https://www.amazon.com/ap/adam https://www.amazon.com/gp/help/customer/display.html%3FnodeId%3DGSNRZP4F7NYG36N6

import Alexa from 'ask-sdk-core';
import * as Model from 'ask-sdk-model';
import { type Profile, type TokenSet } from 'next-auth';
import { type OAuthConfig } from 'next-auth/providers';

import { baseUrl } from '../../config/config';

// From https://developer.amazon.com/settings/console/securityprofile/web-settings/update.html:
const clientId = String(process.env.NEXT_PUBLIC_LWA_CLIENT_ID);
const clientSecret = String(process.env.LWA_CLIENT_SECRET);
const oauth2BaseUrl = 'https://www.amazon.com/ap/oa';
const tokenEndpoint = 'https://api.amazon.com/auth/o2/token';
const userInfoUrl = 'https://api.amazon.com/user/profile';
const amazonAlexaApiEndpoint = 'https://api.amazonalexa.com';

const { services } = Model;
const { LwaServiceClient } = services;

const id = 'alexa';

const authorization = {
  params: {
    redirect_uri: `${baseUrl}/api/auth/callback/${id}`,
    response_type: 'code',

    scope: 'profile profile:user_id postal_code', // https://developer.amazon.com/docs/login-with-amazon/customer-profile.html
  },
  url: oauth2BaseUrl,
};

console.log({ authorization });

/**
 * https://next-auth.js.org/configuration/providers/oauth#userinfo-option
 */
const userinfo: OAuthConfig<Profile>['userinfo'] = {
  // The result of this method will be the input to the `profile` callback.
  async request(context) {
    const accessToken = String(context.tokens.access_token);
    const input = {
      headers: {
        Accept: 'application/json',
        Authorization: `Bearer ${accessToken}`,
      },
      method: 'GET',
    };
    console.log({ input, userInfoUrl });
    const response = await fetch(userInfoUrl, input); // https://developer.amazon.com/docs/login-with-amazon/obtain-customer-profile.html#call-profile-endpoint
    const json = await response.json();
    const alexaUserId = json?.user_id;
    const profile: Profile = {
      id: alexaUserId,
      ...json,
    };
    console.log({ alexaUserId, json, profile });

    return profile;
  },
};

export const alexaProvider: OAuthConfig<Profile> = {
  authorization,
  clientId,
  clientSecret,
  id,
  idToken: false,
  name: 'Alexa',
  profile(profile: Profile, tokenSet: TokenSet) {
    console.log({ profile, tokenSet });
    profile.tokenSet = tokenSet;
    return profile;
  },
  token: {
    // https://next-auth.js.org/configuration/providers/oauth#token-option
    params: {
      grant_type: 'authorization_code',
    },
    url: tokenEndpoint,
  },
  type: 'oauth' as const,
  userinfo,
};

export async function getAccessTokenForScope(scope: string) {
  const apiConfiguration = {
    apiClient: new Alexa.DefaultApiClient(),
    apiEndpoint: amazonAlexaApiEndpoint,
    authorizationValue: '',
  };
  const authenticationConfiguration = {
    clientId,
    clientSecret,
  };

  const lwaServiceClient = new LwaServiceClient({ apiConfiguration, authenticationConfiguration });
  const accessToken = await lwaServiceClient.getAccessTokenForScope(scope);
  return accessToken;
}

The insights:

  1. Using Login With Amazon (LWA) is probably required.
  2. As you can see, there are 4 different Amazon endpoints specified above.
  3. No scopes starting with alexa:: exist anywhere within my repo. Those scopes only belong on the AWS sites below.
  4. My Alexa Client ID and Secret do not exist anywhere within my repo. Those only get used on the AWS sites below.
  5. There are 2 allow-lists that you need to add your redirect URL to. One at https://developer.amazon.com/settings/console/securityprofile/web-settings/view.html?identityAppFamilyId= and one at https://developer.amazon.com/alexa/console/ask/amzn1.ask.skill....
  6. After you set up your Security Profile, go to https://developer.amazon.com/loginwithamazon/console/site/lwa/overview.html (Login With Amazon) to create a new "Login with Amazon Configuration" linking to that.
  7. See important AWS dev console settings below.

Alexa settings Alexa permissions

Submerge answered 26/10, 2023 at 11:53 Comment(2)
I have implemented a complete app-to-app account linking flow, however at present users must manually grant the two list permissions (read and write) - were you able to get it working so the list permission is granted as part of the app-to-app linking flow as I'm stuck here :(Rubrician
@Rubrician I was surprised that my flow doesn't explicitly ask for permission about Alexa lists, yet they seem to work. See the flow at smarthomelist.com I'll be curious for your feedback.Submerge
R
0
  1. Go to the Amazon Developer Console.
  2. Click on "Login with Amazon" in the top-right menu.
  3. Click on "Create a New Security Profile".
  4. Fill in the required information and click on "Save". In the "Web Settings" tab of your new security profile, add your redirectUrl to the "Allowed Return URLs".
  5. Note the "Client ID" and "Client Secret" provided in the "General" tab. Use this new Client ID in your OAuth2 request.

And update your OAuth2 request URL to include the correct client ID and requested scopes for Alexa Lists

const oauth2BaseUrl = 'https://www.amazon.com/ap/oa';
const clientId = 'YOUR_LOGIN_WITH_AMAZON_CLIENT_ID';
const redirectUrl = 'YOUR_REDIRECT_URL';
const scope = 'alexa::household:lists:read alexa::household:lists:write';

const url = `${oauth2BaseUrl}?client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodeURIComponent(redirectUrl)}&response_type=code&scope=${encodeURIComponent(scope)}`;

Next-auth does not directly support Alexa Lists API. Next-auth is an authentication library for Next.js, which simplifies adding authentication to your app. While it supports Login with Amazon, it does not provide built-in support for the Alexa Lists API.

You can use the amazon provider in Next-auth for Login with Amazon, but you would still need to handle Alexa Lists API calls yourself.

For the APP:

Create a component in your Next.js app to generate the OAuth URL and render the button:

// components/LinkWithAmazon.tsx

import React from 'react';

const oauth2BaseUrl = 'https://www.amazon.com/ap/oa';
const clientId = process.env.CLIENT_ID;
const redirectUrl = process.env.REDIRECT_URL;
const scope = 'alexa::household:lists:read alexa::household:lists:write';

const url = `${oauth2BaseUrl}?client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodeURIComponent(redirectUrl)}&response_type=code&scope=${encodeURIComponent(scope)}`;

const LinkWithAmazon: React.FC = () => (
  <a href={url}>Link with Amazon Alexa</a>
);

export default LinkWithAmazon;

Implement a Next.js API route to handle the callback from Amazon and exchange the authorization code for an access token:

// pages/api/auth/callback.ts

import type { NextApiRequest, NextApiResponse } from 'next';
import axios from 'axios';

async function handler(req: NextApiRequest, res: NextApiResponse) {
  const { code } = req.query;

  const response = await axios.post('https://api.amazon.com/auth/o2/token', null, {
    params: {
      grant_type: 'authorization_code',
      code,
      client_id: process.env.CLIENT_ID,
      client_secret: process.env.CLIENT_SECRET,
      redirect_uri: process.env.REDIRECT_URL,
    },
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  });

  const { access_token: accessToken, refresh_token: refreshToken } = response.data;

  // Save the access token and refresh token to your database or session

  // Redirect the user back to your app
  res.redirect('/');
}

export default handler;
···

Use the ·LinkWithAmazon· component in your app to render the button and call the getShoppingList function when needed:
jsx
Resonance answered 17/4, 2023 at 8:14 Comment(2)
I appreciate your answer! I tried to follow it exactly, but I still get the "An unknown scope was requested" error. I think https://mcmap.net/q/743346/-how-can-users-grant-permission-for-my-website-to-manage-their-amazon-alexa-lists might be correct about alexa::household:lists:read alexa::household:lists:write not being OAuth scopes. Also, I'm not sure the correct settings for developer.amazon.com/alexa/console/ask{____}/development/en_US/account-linking (the Account Linking settings and Permissions settings). Thanks.Submerge
See also #75968498 Thanks!Submerge

© 2022 - 2024 — McMap. All rights reserved.