Your Alexa Dashboards Settings

Authorize from a Companion App (Android/iOS)

To access the Alexa Voice Service (AVS), your Alexa-enabled product needs to obtain a Login with Amazon (LWA) access token, which is sent with each request to AVS. If your product lacks a graphical user interface (GUI), also known as headless, you do this by integrating a companion app with the LWA Mobile SDK for Android or iOS. Your companion app is responsible for obtaining an authorization code and securely transferring it to your product. Your product is responsible for using the authorization code to obtain access and refresh tokens from LWA, which are used to make calls to AVS. This document explains how to do this.

Alternatively, if you want to authorize a headless product to use AVS with a web service, see Authorizing from a Companion Site.

When to Use This Method

Scenario: You want to authorize a headless device, like a smart speaker, to access AVS and associate it with a customer’s account using a companion app.

Prerequisites

Before you integrate the LWA SDK into your companion app, your product must be able generate a code verifier and create a code challenge. These values along with the code challenge method are used by LWA to validate requests from your product before tokens are exchanged. The LWA implementation of symmetric proof of possession is based on Proof Key for Code Exchange by OAuth Public Clients

  • Code Verifier

    A code verifier is a cryptographically random string generated by your product, which is hashed (SHA256) and handed off to your companion app. The string should be between 43 and 128 characters long and composed of characters from the URL and filename-safe alphabet ([A-Z], [a-z], [0-9], “-“, “_ “, “.”, “~”).

    The code verifier is sensitive data and should never be transferred from your product.

  • Code Challenge

    Your client/product is expected to create a code challenge derived from the code verifier using SHA-256:

    • S256
      A Base64url encoding of your code verifier’s SHA256 hash. The Base64url encoded string should not contain the following characters: “=”, “+”, or “/”.

    See Proof Key for Code Exchange by OAuth Public Clients, Appendix A for detailed information on implementing Base64url encoding.

  • Code Challenge Method

    The method used to derive the code challenge. AVS requires SHA-256.

Procedure for Obtaining Refresh and Access Tokens

LWA Companion App Flow
Click to enlarge
  1. Your companion app requests the following values from your Alexa-enabled product:
    • Product ID
    • DSN
    • Code Challenge
  2. Your product sends these values to your companion app, and your app displays a Login with Amazon Button to the user.
  3. When the LWA Button is pressed, the user, along with the values requested in step 1, is redirected to an Amazon login page and prompted to enter their Amazon credentials and consent.
  4. After consenting, the user is redirected back to your companion app along with an LWA authorization code, which is valid for 5 minutes and a single use.
  5. Your companion app securely transmits the following values to your Alexa-enabled product:
    • Authorization Code
    • Client ID
    • Redirect URI
  6. Your product calls LWA, and the Authorization Code, Client ID, Redirect URI and Code Verifier are exchanged for a refresh token and access token.
  7. LWA returns a refresh token and access token to your product.
  8. Your product uses the access token to make calls to AVS.
  9. When your access token expires, exchange the Client ID from step 5 and your refresh token to request a new refresh and access token from LWA. Repeat this step every time your access token expires.
  10. LWA returns a new refresh token and access token to your product.

Sample Code

Sample companion apps for Android and iOS are available as part of the [Sample App][avs-pi]. We recommend reviewing the sample code as a functional example of a companion app or if you encounter any issues below.

Use the Login with Amazon SDK

Please use the tabs below to select your mobile operating system:

Follow the instructions below to use LWA SDK for Android to pass a LWA authorization code to your product, which your product can then use to obtain refresh and access tokens needed to make calls to AVS.

  • Register a device in the Amazon Developer Console and generate an API key for your product. For instructions on how to create an account and register a product see Getting Started with AVS.
  • Navigate to Login with Amazon Getting Started for Android and complete steps 1, 4, and 5.
  • Then complete the following steps to obtain an LWA access token to use with AVS:
  1. Import the Login with Amazon API to your source file. To import the Login with Amazon API, add the following import statements to your source file:

    import com.amazon.identity.auth.device.AuthError;
    import com.amazon.identity.auth.device.api.authorization.AuthCancellation;
    import com.amazon.identity.auth.device.api.authorization.AuthorizationManager;
    import com.amazon.identity.auth.device.api.authorization.AuthorizeListener;
    import com.amazon.identity.auth.device.api.authorization.AuthorizeRequest;
    import com.amazon.identity.auth.device.api.authorization.AuthorizeResult;
    import com.amazon.identity.auth.device.api.authorization.ScopeFactory;
    import com.amazon.identity.auth.device.api.workflow.RequestContext;
    
  2. Initialize the RequestContext.

    You will need to declare a RequestContext member variable and create a new instance of the class. To create the instance, pass in the current application context to the static factory method. The best place to initialize the RequestContext is in the onCreate method of your Activity. For example:

    private RequestContext mRequestContext;
    
    @Override
    protected void onCreate(Bundle savedInstance) {
        super.onCreate(savedInstance);
        mRequestContext = RequestContext.create(this);
    }
    
  3. Create an AuthorizeListenerImpl.

    AuthorizeListenerImpl extends the AuthorizeListener abstract class and will process the result of the authorize call. It contains three methods: onSuccess, onError, and onCancel. The AuthorizeResult object passed into the onSuccess method will contain the values needed to obtain an authorization token.

    private class AuthorizeListenerImpl extends AuthorizeListener {
    
        /* Authorization was completed successfully. */
        @Override
        public void onSuccess(final AuthorizeResult authorizeResult) {
        }
    
        /* There was an error during the attempt to authorize the application. */
        @Override
        public void onError(final AuthError authError) {
        }
    
        /* Authorization was cancelled before it could be completed. */
        @Override
        public void onCancel(final AuthCancellation authCancellation) {
        }
    }
    
  4. Create an instance of AuthorizeListenerImpl and register it.

    Create an instance of your AuthorizeListenerImpl and register it with your RequestContext instance. When the AuthorizeManager.authorize call is made, your RequestContext instance will invoke the appropriate callback method in your AuthorizeListener with the result of the authorize request. The best place to register your AuthorizeListenerImpl instance is in the onCreate method.

    @Override
    protected void onCreate(Bundle savedInstance) {
      super.onCreate(savedInstance);
      mRequestContext = RequestContext.create(this);
      mRequestContext.registerListener(new AuthorizeListenerImpl());
    }
    
  5. Override the onResume method.

    Override the onResume method in your Activity. Call super.onResume() as well as the onResume method on your RequestContext instance. This will notify the RequestContext to invoke your AuthorizeListener when your app is resumed if there is a callback ready from your AuthorizeManager.authorize call.

    @Override
    protected void onResume() {
        super.onResume();
        mRequestContext.onResume();
    }
    
  6. Call AuthorizeManager.authorize.

    In the onClick handler for your Login with Amazon button, call authorize to prompt the user to log in and authorize your application. This method is responsible for authorizing the customer in one of the following ways:

    • Switches to the system browser and lets the customer sign in and consent to the requested information.

    • Switches to web view in a secure context to let the customer sign in and consent to the requested information.


    This secure context for the second point above, is currently made available as the Amazon Shopping app on Android devices. Amazon devices running Fire OS (for example Kindle tablets and Fire TV devices) always use this option even if there is no Amazon Shopping app on the device. Because of this, if the customer is already signed in to the Amazon Shopping app, this API will skip the sign-in page, leading to a Single Sign On experience for the customer.

    When your application is authorized, it is authorized for one or more data sets known as scopes. Scopes are an OAuth mechanism for a client to tell an authorization server what resource permissions they need. You pass an array of scopes as a parameter to the authorize method.

    The first time a user logs in to your app, they will receive a prompt that contains the list of data you require access to, and request consent before proceeding. LWA when used with AVS requires the alexa:all scope.

    The call to authorize is asynchronous, and your AuthorizeListenerImpl instance will be invoked with the result of your call.

    private RequestContext mRequestContext;
    private static final String PRODUCT_ID = "INSERT YOUR PRODUCT ID FROM AMAZON DEVELOPER CONSOLE";
    private static final String PRODUCT_DSN = "INSERT UNIQUE DSN FOR YOUR DEVICE";
    private static final String CODE_CHALLENGE = "INSERT CODE CHALLENGE FROM DEVICE FOR THIS REQUEST";
    private static final String CODE_CHALLENGE_METHOD = "S256";
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mRequestContext = RequestContext.create(this);
        mRequestContext.registerListener(new AuthorizeListenerImpl());
    
        // Find the button with the login_with_amazon ID
        // and set up a click handler
        mLoginButton = (Button) findViewById(R.id.login_with_amazon);
        mLoginButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                final JSONObject scopeData = new JSONObject();
                final JSONObject productInstanceAttributes = new JSONObject();
    
                try {
                    productInstanceAttributes.put("deviceSerialNumber", PRODUCT_DSN);
                    scopeData.put("productInstanceAttributes", productInstanceAttributes);
                    scopeData.put("productID", PRODUCT_ID);
    
                    AuthorizationManager.authorize(new AuthorizeRequest.Builder(mRequestContext)
                        .addScope(ScopeFactory.scopeNamed("alexa:all", scopeData))
                        .forGrantType(AuthorizeRequest.GrantType.AUTHORIZATION_CODE)
                        .withProofKeyParameters(CODE_CHALLENGE, CODE_CHALLENGE_METHOD)
                        .build());
                } catch (JSONException e) {
                    // handle exception here
                }
            }
        });
    }
    

    In the code above we are adding the following options:

    • scopeData is extra data that includes the product type ID (PRODUCT_ID) and the product’s unique DSN (PRODUCT_DSN), that is bound to the alexa:all scope.
    • A constant which specifies that the authorization code should be returned.
    • The code challenge. For Base64 URL encoding we recommend using Android’s Base64 library with the following flags: Base64.NO_PADDING, Base64.URL_SAFE, and Base64.NO_WRAP.
    • The code challenge’s encoding method (S256).

    The authorization code you’ve obtained is valid for 5 minutes and a single use.

  7. Implement onSuccess for your AuthorizeListenerImpl.

    Retrieve the authorization code, redirect URI, and client id from the AuthorizeResult to be sent securely to your product.

    private class AuthorizeListenerImpl extends AuthorizeListener {
        @Override
        public void onSuccess(final AuthorizeResult authorizeResult) {
            final String authorizationCode = authorizeResult.getAuthorizationCode();
            final String redirectUri = authorizeResult.getRedirectUri();
            final String clientId = authorizeResult.getClientId();
            // Securely send the authorization code, redirectUri, and clientId to the product
        }
        // …
        // Other methods from AuthorizeListener
    }
    
  8. Once the authorization code, client ID, and redirect URI are received on the product, the product should call LWA in order to exchange the authorization code for access and refresh tokens. When making the call, the product needs to send a POST request to https://api.amazon.com/auth/O2/token and pass in the following parameters:

    • grant_type: authorization_code
    • code: The authorization code received from the Android app.
    • redirect_uri: The redirect URI received from the Android app.
    • client_id: The client ID received from the Android app.
    • code_verifier: The code verifier that was initially generated by the product.

    The response includes the following values:

    • access_token: The access token.
    • refresh_token: The refresh token.
    • token_type: bearer
    • expires_in: The number of seconds for which the access token is still valid.
  9. When the access token has expired or is about to expire, the refresh token can be exchanged for new access and refresh tokens. To do the exchange, send POST request to https://api.amazon.com/auth/O2/token with the following parameters:

    • grant_type: refresh_token
    • refresh_token: The refresh token.
    • client_id: The client ID received from the Android app.

    The response includes the following values:

    • access_token: The access token.
    • refresh_token: The refresh token.
    • token_type: bearer
    • expires_in: The number of seconds for which the access token is still valid.

Follow the instructions below to use Login with Amazon SDK for iOS to pass a Login with Amazon authorization code to your product, which your product can then use to obtain refresh and access tokens needed to make calls to AVS.

  • Register a device in the Amazon Developer Console and generate an API key for your product. For instructions on how to create an account and register a product see Getting Started with AVS.
  • Navigate to Login with Amazon Getting Started for iOS and complete steps 1, 4, and 5.
  • Then complete the following steps to obtain a Login with Amazon access token to use with AVS:
  1. Import the Login with Amazon API to your source file.

    To import the Login with Amazon API, add the following #import statement to your source file:

    #import <LoginWithAmazon/LoginWithAmazon.h>
    
  2. Call authorize:withHandler: in onLoginButtonClicked.

    If you followed the steps to Add a Login with Amazon Button to Your App, you should have an onLoginButtonClicked: method linked to a Login with Amazon button. In that method, call authorize:withHandler: to prompt the user to login and authorize your application.

    This method will enable the user to sign-in and consent to the requested information in one of the following ways:

    1. Switches to web view in a secure context (if the Amazon Shopping app is installed to the device)
    2. Switches to Safari View Controller (on iOS 9 and later)
    3. Switches to the system browser (on iOS 8 and earlier)

    The secure context for the first option is available when the Amazon Shopping app is installed to the device. If the user is already signed in to the Amazon Shopping app, this API will skip the sign in page, leading to a Single Sign-On (SSO) experience.

    The first parameter to authorize:withHandler: is an AMZNAuthorizeRequest object that indicates what scope your application is requesting authorization for. A scope encompasses the user data you are requesting from Login with Amazon. The first time a user logs in to your app, they will be presented with a list of the data you are requesting and asked for approval.

    The second parameter to authorize:withHandler: is AMZNAuthorizationRequestHandler, described in the next step (for code sample, see step 3).

  3. Create an AMZNAuthorizationRequestHandler block object.

    AMZNAuthorizationRequestHandler processes the result of the authorize:withHandler: call. To learn more about objective-c blocks, see Working with Blocks on developer.apple.com.

    The first parameter of AMZNAuthorizationRequestHandler is an AMZNAuthorizeResult object. After a user is authorized successfully, AMZNAuthorizeResult will contain an access token which can be used to access a user’s profile data, and an AMZNUser object, which contains the user’s profile data.

    The second parameter of AMZNAuthorizationRequestHandler is a Boolean called userDidCancel. This parameter will be set to true if the user:

    • Closes the Safari View Controller during login and authorization (on iOS 9 and later)
    • Closes the web view in the Amazon Shopping app
    • Cancels the login or reject authorization

    The third parameter of AMZNAuthorizationRequestHandler is an NSError object which contains error details if the login and authorization fails due to the SDK or authorization server.

    NSString *productId = @"INSERT YOUR PRODUCT ID FROM AMAZON DEVELOPER CONSOLE";
    NSString *productDsn = @"INSERT UNIQUE DSN FOR YOUR DEVICE";
    NSString *codeChallenge = @"INSERT CODE CHALLENGE FROM DEVICE FOR THIS REQUEST";
    
    - (IBAction) onLogInButtonClicked:(id)sender {
      NSDictionary *scopeData = @{@"productID": productId,
                                  @"productInstanceAttributes": @{@"deviceSerialNumber": productDsn}};
    
      id alexaAllScope = [AMZNScopeFactory scopeWithName:@"alexa:all" data:scopeData];
    
      AMZNAuthorizeRequest *request = [[AMZNAuthorizeRequest alloc] init];
      request.scopes = @[alexaAllScope];
      request.codeChallenge = codeChallenge;
      request.codeChallengeMethod = @"S256";
      request.grantType = AMZNAuthorizationGrantTypeCode;
    
      AMZNAuthorizationManager *authManager = [AMZNAuthorizationManager sharedManager];
      [authManager authorize:request withHandler:^(AMZNAuthorizeResult *result, BOOL userDidCancel, NSError *error) {
          if (error) {
              // Notify the user that authorization failed
              [[[UIAlertView alloc] initWithTitle:@"" message:[NSString stringWithFormat:@"User authorization failed due to an error: %@", error.localizedDescription] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
          } else if (userDidCancel) {
              // Notify the user that the authorization was cancelled
              [[[UIAlertView alloc] initWithTitle:@"" message:@"Authorization was cancelled prior to completion. To continue, you will need to try logging in again." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
          } else {
              // Fetch the authorization code and return to controller
              self.authCode = result.authorizationCode;
              self.clientId = result.clientId;
              self.redirectUri = result.redirectUri;
              [self userSuccessfullySignedIn];
          }
      }];
    }
    
  4. Securely transfer the authorization code, client ID, and redirect URI to your product.

  5. Once the authorization code, client ID, and redirect URI are received on the product, the product should call LWA in order to exchange the authorization code for access and refresh tokens. When making the call, the product needs to send a POST request to https://api.amazon.com/auth/O2/token and pass in the following parameters:

    • grant_type: authorization_code
    • code: The authorization code received from the iOS app.
    • redirect_uri: The redirect URI received from the iOS app.
    • client_id: The client ID received from the iOS app.
    • code_verifier: The code verifier that was initially generated by the product.

    The response includes the following values:

    • access_token: The access token.
    • refresh_token: The refresh token.
    • token_type: bearer
    • expires_in: The number of seconds for which the access token is still valid.
  6. When the access token has expired or is about to expire, the refresh token can be exchanged for new access and refresh tokens. To do the exchange, POST to https://api.amazon.com/auth/O2/token and send the following parameters:

    • grant_type: refresh_token
    • refresh_token: The refresh token.
    • client_id: The client ID received from the iOS app.

    The response includes the following values:

    • access_token: The access token.
    • refresh_token: The refresh token.
    • token_type: bearer
    • expires_in: The number of seconds for which the access token is still valid.

Resources