Authorize from an AVS Product (Android/iOS)

To access the Alexa Voice Service (AVS), your Alexa Built-in Android or iOS app needs to obtain a Login with Amazon (LWA) access token to send with each request to the Alexa Voice Service. In this scenario, you don't have a headless device, such as a smart speaker, that requires you to authorize and associate the device with a user account.

This document explains how to obtain an access token with the LWA SDK.

Using the Login with Amazon SDK

Use the following tabs to select your operating system:

Use the following instructions to add LWA to your Android app:

  • Register a mobile app in the Alexa developer portal and generate an API key for your app. For instructions on how to create an account and register an app see Getting Started with AVS.
  • Navigate to the 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. Add LWA to your Android project. For installation instructions, see Create a Login with Amazon Project: Install the Login with Amazon Library.

  2. Import the LWA API to your source file. To import the LWA API, add the following import statements to your source file:

    import com.amazon.identity.auth.device.AuthError;
    import com.amazon.identity.auth.device.api.Listener;
    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.Scope;
    import com.amazon.identity.auth.device.api.authorization.ScopeFactory;
    import com.amazon.identity.auth.device.api.workflow.RequestContext;
    
  3. Initialize RequestContext.

    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);
    }
    
  4. Create an AuthorizeListenerImpl.

    AuthorizeListenerImpl extends the AuthorizeListener abstract class and processes the result of the authorize call. It contains three methods: onSuccess, onError, and onCancel. The AuthorizeResult object passed into the onSuccess method contains the access 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) {
        }
    }
    
  5. Create and register an instance of AuthorizeListenerImpl.

    Create an instance of your AuthorizeListenerImpl and register it with your RequestContext instance. When making the AuthorizeManager.authorize call, 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());
    }
    
  6. 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 notifies the RequestContext to invoke your AuthorizeListener when your app resumes if there is a callback ready from your AuthorizeManager.authorize call.

    @Override
    protected void onResume() {
        super.onResume();
        mRequestContext.onResume();
    }
    
  7. 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 app. This method authorizes 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.

      The secure context is available through the Amazon Shopping app on Android devices. Amazon devices running Fire OS, such as Kindle tablets and Fire TV devices, always use this option even if the device doesn't have the Amazon Shopping app installed. Because of this, if the customer is already signed in to the Amazon Shopping app, this API skips the sign-in page, leading to a Single Sign On (SSO) experience for the customer.

    Your app is then authorized for one or more data sets called "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 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 alexa:voice_service:pre_auth scope enables the AVS Hosted Splash for the user.

    The call to authorize is asynchronous, and the result of your call invokes an AuthorizeListenerImpl instance.

    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";
    
    @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)
                        .addScopes(ScopeFactory.scopeNamed("alexa:voice_service:pre_auth"),
                                ScopeFactory.scopeNamed("alexa:all", scopeData))
                        .forGrantType(AuthorizeRequest.GrantType.ACCESS_TOKEN)
    		                .shouldReturnUserData(false)
                        .build());
                } catch (JSONException e) {
                    // handle exception here
                }
            }
        });
    }
    
  8. Create a TokenListener.

    Implement the Listener<AuthorizeResult, AuthError> interface. Your listener processes the result of the getToken call. This Listener contains two methods, onSuccess and onError (it doesn't support onCancel because there is no way to cancel a getToken call). onSuccess receives an AuthorizeResult object with token data, and onError receives an AuthError object with information about the error.

    public class TokenListener implements Listener<AuthorizeResult, AuthError> {
        /* getToken completed successfully. */
        @Override
        public void onSuccess(AuthorizeResult authorizeResult) {
        }
    
        /* There was an error during the attempt to get the token. */
        @Override
        public void onError(AuthError authError) {
        }
    }
    
  9. Implement onSuccess for your AuthorizeListener.

    In onSuccess, call AmazonAuthorizationManager.getToken to retrieve the access token. getToken, like authorize, uses an asynchronous listener interface. For getToken, that interface is Listener<AuthorizeResult, AuthError>, not AuthorizationListener.

    @Override
    public void onSuccess(Bundle response) {
        AuthorizationManager.getToken(this, new Scope[] { ScopeFactory.scopeNamed("alexa:all") }, new TokenListener());
    }
    
  10. Implement onSuccess for your TokenListener.

    onSuccess has two tasks:

    • To retrieve the access token from the Bundle.
    • To set the UI state to logged in, which indicates that a user is logged in and provides a way to log out.
    /* getToken completed successfully. */
    @Override
    public void onSuccess(AuthorizeResult authorizeResult) {
        String accessToken = authorizeResult.getAccessToken();
    }
    
  11. In the onStart method of your Activity, call getToken to see if the app is still authorized.

    • getToken retrieves the raw access token to use when making requests to AVS.
    • getToken requires the same scopes you requested in your call to authorize.
    private static final Scope ALEXA_ALL_SCOPE = ScopeFactory.scopeNamed("alexa:all");
    
    @Override
    protected void onStart(){
        super.onStart();
        AuthorizationManager.getToken(this, new Scope[] { ALEXA_ALL_SCOPE }, new TokenListener());
    }
    
  12. Now you have an access token to use for requests to AVS.

Use the following instructions to integrate LWA with your iOS app.

  • Register a mobile app in the Alexa developer portal and generate an API key for your app. For instructions on how to create an account and register an app 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 an LWA access token to use with AVS:
  1. Add LWA to your iOS project. For instructions, see Create a Login with Amazon Project: Install the Login with Amazon Library.

  2. Import the LWA API to your source file.

    To import the LWA API, add the following #import statements to your source file:

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

    If you followed the steps in 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 app.

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

    • Switches to web view in a secure context (if the device has the Amazon Shopping app installed).
    • Switches to Safari View Controller (on iOS 9 and later).
    • 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 skips the sign in page, leading to a Single Sign-On (SSO) experience.

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

    The second parameter to authorize:withHandler: is AMZNAuthorizationRequestHandler, described in the next step.

  4. 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.

    Your app is then 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 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 alexa:voice_service:pre_auth scope enables the AVS Hosted Splash for the user.

    The first parameter of AMZNAuthorizationRequestHandler is an AMZNAuthorizeResult object. After authorizing the user, AMZNAuthorizeResult contains an access token to access user profile data and an AMZNUser object, which contains the user profile data.

    The second parameter of AMZNAuthorizationRequestHandler is a Boolean called userDidCancel. This parameter becomes true if the user performs any of the following actions:

    • 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 or authorization fails due to the SDK or the authorization server.

    ```java NSString *productId = @"INSERT YOUR PRODUCT ID FROM AMAZON DEVELOPER CONSOLE"; NSString *productDsn = @"INSERT UNIQUE DSN FOR YOUR DEVICE";

    • (IBAction) onLogInButtonClicked:(id)sender { NSDictionary *scopeData = @{@"productID": productId, @"productInstanceAttributes": @{@"deviceSerialNumber": productDsn}};

      id alexaAllScope = [AMZNScopeFactory scopeWithName:@"alexa:all" data:scopeData];

      id alexaSplashScope = [AMZNScopeFactory scopeWithName:@"alexa:voice_service:pre_auth"];

      AMZNAuthorizeRequest *request = [[AMZNAuthorizeRequest alloc] init]; request.scopes = @[alexaSplashScope, alexaAllScope]; request.grantType = AMZNAuthorizationGrantTypeToken;

      //Make an Authorize call to the LWA SDK. 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 access token and return to controller self.token = result.token; [self userSuccessfullySignedIn];

        }   }]; } ``
      
  5. Check for user login at startup.

    If a user logs into your app, closes the app, and restarts the app later, the app is still authorized to retrieve data. The user isn't logged out automatically. At startup, you can show the user as logged in if your app is still authorized. This section explains how to use authorize:withHandler: to see if the app is still authorized.

    1. Create an AMZNAuthorizeRequest object and specify scopes that indicate the user data your application is requesting authorization for. For more information, see step 3.
    2. Set AMZNAuthorizeRequest.interactiveStrategy to AMZNInteractiveStrategyNever. AMZNAuthorizeRequest supports multiple strategies for prompting user login:

      • AMZNInteractiveStrategyAuto (default): The SDK looks for a locally stored authorization grant from previous authorize:withHandler: responses. If one is available, valid, and contains all requested scopes, the SDK will return a successful response via AMZNAuthorizationRequestHandler, and doesn't prompt the user to login. Otherwise, the user will be prompted to login.
      • AMZNInteractiveStrategyAlways: The SDK always prompts the user to login regardless of whether they were already authorized to use the app. When the user is prompted, the SDK removes all locally cached authorization grants for the app.
      • AMZNInteractiveStrategyNever: The SDK looks for a locally stored authorization grant from previous authorize:withHandler responses. If one is available, valid, and contains all requested scopes, the SDK will return an AMZNAuthorizeResult object that contains an access token and user profile data. Otherwise, it returns an NSError object via AMZNAuthorizationRequestHandler.
    // Build an authorize request.
    AMZNAuthorizeRequest *request = [[AMZNAuthorizeRequest alloc] init];
    request.scopes = @[alexaAllScope];
    request.grantType = AMZNAuthorizationGrantTypeToken;
    
    request.interactiveStrategy = AMZNInteractiveStrategyNever;
    
    [AMZNAuthorizationManager sharedManager] authorize:request
    withHandler:^(AMZNAuthorizeResult *result BOOL
    userDidCancel NSError *error) {
       if (error) {
            // Error from the SDK, indicating the user was not previously authorized to your app for the requested scopes.
       } else {
            // Fetch the access token and return to controller
            self.token = result.token;
            [self userSuccessfullySignedIn];
       }
    }];
    
  6. Implement application:openURL:sourceApplication:annotation: in the class in your project that handles the UIApplicationDelegate protocol.

    By default this will be the AppDelegate class in your project. When the app presents the Amazon login page, if the user completes log in, it will redirect to the app using the URL Scheme the app registered earlier. That redirect is passed to application:openURL:sourceApplication:annotation:application:openURL:sourceApplication:annotation: returns YES if the URL was successfully handled.

    handleOpenURL:sourceApplication: is an SDK library function that will handle LWA redirect URLs for you. If handleOpenURL:sourceApplication: returns YES, then the URL was handled.

    - (BOOL)application:(UIApplication *)application
       openURL:(NSURL *)url
       sourceApplication:(NSString *)sourceApplication
       annotation:(id)annotation
    {
        // Pass on the url to the SDK to parse authorization code from the url.
    
        BOOL isValidRedirectSignInURL =
          [AIMobileLib handleOpenURL:url sourceApplication:sourceApplication];
        if(!isValidRedirectSignInURL)
            return NO;
    
        // App may also want to handle url
        return YES;
    }
    
  7. Now you have an access token to use for requests to AVS.

Resources