コンパニオンアプリからの認可 (Android/iOS)



概要

Alexa Voice Service(AVS)にアクセスするには、Alexa搭載製品でLogin with Amazon(LWA)のアクセストークンを取得する必要があります。このアクセストークンを、リクエストのたびにAVSに送信します。製品が、グラフィカルユーザーインターフェース(GUI)を持たない(ヘッドレス)の場合、コンパニオンアプリとLWA Mobile SDK for AndroidまたはiOSを統合することにより、アクセストークンを取得します。コンパニオンアプリには、認可コードを取得して安全に製品に転送するという役割があります。一方、製品には、認可コードを使用し、LWAから、AVSへの呼び出しを行うために使用するアクセストークンとリフレッシュトークンを取得するという役割があります。このドキュメントでは、その方法について説明します。

これとは別に、ヘッドレス製品をウェブサービスでAVSを使用するよう認可する場合は、コンパニオンサイトからの認可を参照してください。

この方法を使うケース

シナリオ: コンパニオンアプリを使用して、スマートスピーカーなどのヘッドレス端末を、AVSにアクセスし、ユーザーのアカウントと関連付けるよう、認可しようとしています。

前提条件

コンパニオンアプリにLWA SDKを統合する前に、製品はコードベリファイアを生成し、チャレンジコードを作成できる必要があります。チャレンジコードメソッドで指定されるこれらの値は、LWAがトークンの交換前に製品からのリクエストを検証するために使用します。保持の対称証明のLWA実装は、Proof Key for Code Exchange by OAuth Public Clientsに基づいています。

  • コードベリファイア

    コードベリファイアは、製品によって生成された、暗号化されたランダムな文字列で、ハッシュ化され(SHA256)、コンパニオンアプリにハンドオフされます。文字列の長さは43~128文字で、URLとファイル名に使用できる文字([A-Z]、[a-z]、[0-9]、「-」、「_」、「.」、「~」)で構成されます。

    コードベリファイアは機密データのため、絶対に製品外に送信しないでください。

  • チャレンジコード

    クライアント/製品は、以下のSHA-256を使用してコードベリファイアから抽出したチャレンジコードを作成する必要があります。

    • S256
      コードベリファイアをSHA256でハッシュ化した値をBase64urlでエンコードしたもの。Base64urlエンコードした文字列に以下の文字を含めることはできません: 「=」、「+」、「/」

    Base64urlエンコーディングの実装方法の詳細については、Proof Key for Code Exchange by OAuth Public Clientsの付録Aを参照してください。

  • チャレンジコードメソッド

    チャレンジコードを抽出するために使用するメソッド。AVSでは、SHA-256を使用する必要があります。

リフレッシュトークンおよびアクセストークンの取得手順

LWA Companion App Flow
クリックして拡大
  1. コンパニオンアプリは、Alexa搭載製品から以下の値をリクエストします。
    • 製品ID
    • DSN
    • チャレンジコード
  2. 製品がこれらの値をコンパニオンアプリに送信すると、アプリはユーザーにLogin with Amazonボタンを表示します。
  3. LWAボタンが押されると、ユーザーはステップ1でリクエストした値とともにAmazonのログインページにリダイレクトされ、Amazon認証情報を入力し承諾するよう求められます。
  4. 承諾したら、ユーザーは、5分間有効で1回限り使用できるLWA認可コードとともにコンパニオンアプリに再びリダイレクトされます。
  5. コンパニオンアプリは、Alexa搭載製品に以下の値を安全に送信します。
    • 認可コード
    • クライアントID
    • リダイレクトURI
  6. 製品はLWAを呼び出し、リフレッシュトークンとアクセストークンと引き換えに、認可コード、クライアントID、リダイレクトURIおよびコードベリファイアを送信します。
  7. LWAは、リフレッシュトークンとアクセストークンを製品に返します。
  8. 製品は、AVSの呼び出しにアクセストークンを使用します。
  9. アクセストークンの有効期限が切れると、LWAから新しいリフレッシュトークンとアクセストークンを取得するのと引き換えに、ステップ5のクライアントIDとリフレッシュトークンを送信します。アクセストークンの有効期限が切れるたびにこのステップを繰り返します。
  10. LWAは、新しいリフレッシュトークンとアクセストークンを製品に返します。

サンプルコード

AndroidとiOSのサンプルコンパニオンアプリは、[サンプルアプリ][avs-pi]の一部として入手できます。コンパニオンアプリの機能例として、また以下の問題が発生した場合は、サンプルコードを確認することをお勧めします。

Login with Amazon SDKを使用する

下のタブを使用して、モバイルのオペレーティングシステムを選択してください。

Android用のLWA SDKを使用してLWA認可コードを製品に渡し、AVSの呼び出しに必要なリフレッシュトークンとアクセストークンを取得して使用できるようにするには、以下の説明に従います。

  • Amazon開発者コンソールで端末を登録し、製品のAPIキーを生成します。アカウントを作成して製品を登録する方法については、AVSスタートガイドを参照してください。
  • Android用Login with Amazonに移動し、ステップ1、4、および5を実行します。
  • 次に、以下のステップを実行して、AVSで使用するLWAアクセストークンを取得します。
  1. Login with Amazon APIをソースファイルにインポートします。Login with Amazon APIをインポートするには、以下のimport文をソースファイルに追加します。

    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. RequestContextを初期化します。

    RequestContextメンバー変数を宣言し、クラスの新しいインスタンスを作成する必要があります。インスタンスを作成するには、現在のアプリケーションコンテキストをstaticなファクトリーメソッドに渡します。RequestContextは、アクティビティのonCreateメソッド内で初期化することをお勧めします。例:

    private RequestContext mRequestContext;
    
    @Override
    protected void onCreate(Bundle savedInstance) {
        super.onCreate(savedInstance);
        mRequestContext = RequestContext.create(this);
    }
    
  3. AuthorizeListenerImplを作成します。

    AuthorizeListenerImpl は、AuthorizeListener抽象クラスを拡張し、authorize呼び出しの結果を処理します。次の3つのメソッドが含まれます: onSuccessonError、およびonCancelonSuccessメソッドに渡されるAuthorizeResultオブジェクトには、認可トークンの取得に必要な値が含まれます。

    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. AuthorizeListenerImplのインスタンスを作成し、登録します。

    AuthorizeListenerImplのインスタンスを作成し、RequestContextインスタンスに登録します。AuthorizeManager.authorizeの呼び出しが行われると、RequestContextインスタンスは、authorizeリクエストの結果を使用してAuthorizeListenerで適切なコールバックメソッドを呼び出します。AuthorizeListenerImplインスタンスは、onCreateメソッド内に登録することをお勧めします。

    @Override
    protected void onCreate(Bundle savedInstance) {
      super.onCreate(savedInstance);
      mRequestContext = RequestContext.create(this);
      mRequestContext.registerListener(new AuthorizeListenerImpl());
    }
    
  5. onResumeメソッドをオーバーライドします。

    アクティビティ内のonResumeメソッドをオーバーライドします。RequestContextインスタンス上で、onResumeメソッドだけでなく、super.onResume()も呼び出します。これにより、RequestContextに対して、AuthorizeManager.authorize呼び出しからのコールバックの準備ができた場合にアプリが再開されると、AuthorizeListenerを呼び出すよう通知されます。

    @Override
    protected void onResume() {
        super.onResume();
        mRequestContext.onResume();
    }
    
  6. AuthorizeManager.authorizeを呼び出します。

    Login with AmazonボタンのonClickハンドラーで、authorizeを呼び出し、ユーザーにログインしてアプリケーションを認可するよう求めます。このメソッドは、以下のいずれかの方法でユーザーを認可します。

    • システムブラウザに切り替え、ユーザーにサインインしてリクエストされた情報を承諾するよう求める。

    • セキュアなコンテキストでのウェブ表示に切り替え、ユーザーにサインインしてリクエストされた情報を承諾するよう求める。


    2つ目に記述されているセキュアなコンテキストは、現在、Android端末上のAmazonショッピングアプリとして利用できます。Fire OSを実行しているAmazon端末(KindleタブレットやFire TV端末など)は、端末上にAmazonショッピングアプリがなくても、常にこのオプションを使用します。このため、ユーザーがすでにAmazonショッピングアプリにサインインしていれば、シングルサインオン機能により、このAPIはサインインページをスキップします。

    アプリケーションが認可されると、スコープと呼ばれる1つ以上のデータセットに対して認可されます。スコープはOAuthメカニズムの1つで、認可サーバーに、クライアントで何のリソースパーミッションが必要かを知らせます。authorizeメソッドに、パラメーターとしてスコープの配列を渡します。

    アプリに初めてログインすると、ユーザーに、アクセスする必要のあるデータのリストを含むプロンプトが提示され、続行する前に承諾を求められます。AVSでLWAを使用するときは、alexa:allスコープが必要です。

    認可の呼び出しは非同期で行われ、呼び出しの結果を使用してAuthorizeListenerImplインスタンスが呼び出されます。

    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
                }
            }
        });
    }
    

    上のコードでは、以下のオプションを追加しています。

    • scopeData は製品タイプID(PRODUCT_ID)と製品固有のDSN(PRODUCT_DSN)含む追加のデータで、alexa:allスコープにバインドされています。
    • 認可コードを返す必要があることを示す定数。
    • チャレンジコード。Base64 URLエンコーディングには、AndroidのBase64ライブラリで以下のフラグを使用することをお勧めします: Base64.NO_PADDINGBase64.URL_SAFE、およびBase64.NO_WRAP
    • チャレンジコードのエンコーディングメソッド(S256)。

    取得した認可コードは、5分間有効で1回限り使用できます。

  7. AuthorizeListenerImplonSuccessを実装します。

    AuthorizeResultから、製品に安全に送信する必要のある認可コード、リダイレクトURI、およびクライアントIDを取り出します。

    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. 製品が認可コード、クライアントID、およびリダイレクトURIを受信したら、製品はLWAを呼び出して、アクセストークンとリフレッシュトークンを取得するために認可コードを送信します。呼び出しを行うとき、製品はPOSTリクエストをhttps://api.amazon.com/auth/O2/tokenに送信し、以下のパラメーターを渡す必要があります:

    • grant_type: authorization_code
    • code: Androidアプリから取得した認可コード。
    • redirect_uri: Androidアプリから取得したリダイレクトURI。
    • client_id: Androidアプリから取得したクライアントID。
    • code_verifier: 製品で最初に生成されたコードベリファイア。

    応答には、以下の値が含まれます。

    • access_token: アクセストークン。
    • refresh_token: リフレッシュトークン。
    • token_type: bearer
    • expires_in: アクセストークンの有効期限が切れるまでの秒数。
  9. アクセストークンの有効期限が切れたか、間もなく切れるとき、新しいアクセストークンとリフレッシュトークンを取得するためにリフレッシュトークンを交換できます。そのためには、POSTリクエストをhttps://api.amazon.com/auth/O2/tokenに送信し、以下のパラメーターを渡します:

    • grant_type: refresh_token
    • refresh_token: リフレッシュトークン。
    • client_id: Androidアプリから取得したクライアントID。

    応答には、以下の値が含まれます。

    • access_token: アクセストークン。
    • refresh_token: リフレッシュトークン。
    • token_type: bearer
    • expires_in: アクセストークンの有効期限が切れるまでの秒数。

iOS用のLogin with Amazon SDKを使用してLogin with Amazon認可コードを製品に渡し、AVSの呼び出しに必要なリフレッシュトークンとアクセストークンを取得して使用できるようにするには、以下の説明に従います。

  • Amazon開発者コンソールで端末を登録し、製品のAPIキーを生成します。アカウントを作成して製品を登録する方法については、AVSスタートガイドを参照してください。
  • iOS用Login with Amazonに移動し、ステップ1、4、および5を実行します。
  • 次に、以下のステップを実行して、AVSで使用するLogin with Amazonアクセストークンを取得します。
  1. Login with Amazon APIをソースファイルにインポートします。

    Login with Amazon APIをインポートするには、以下の#import文をソースファイルに追加します。

    #import <LoginWithAmazon/LoginWithAmazon.h>
    
  2. onLoginButtonClickedauthorize:withHandler:を呼び出します。

    アプリにLogin with Amazonボタンを追加するステップを実行した場合、onLoginButtonClicked:メソッドをLogin with Amazonボタンにリンクする必要があります。このメソッド内で、authorize:withHandler:を呼び出し、ユーザーにログインしてアプリケーションを認可するプロンプトを提示します。

    このメソッドを使うと、ユーザーは、以下のいずれかの方法でサインインし、リクエストされた情報を承諾することができます。

    1. セキュアなコンテキストでのウェブ表示に切り替える(端末にAmazon ショッピングアプリがインストールされている場合)
    2. Safari View Controllerに切り替える(iOS 9以上)
    3. システムブラウザに切り替える(iOS 8以下)

    1つ目に記述されているセキュアなコンテキストは、端末にAmazonショッピングアプリがインストールされているときに利用できます。ユーザーがすでにAmazonショッピングアプリにサインインしていれば、シングルサインオン(SSO)機能により、このAPIはサインインページをスキップします。

    authorize:withHandler:への最初のパラメーターは、アプリケーションがどのスコープの認可をリクエストしているかを示すAMZNAuthorizeRequestオブジェクトです。スコープは、Login with Amazonからリクエストしているユーザーデータの範囲です。アプリに最初にログインすると、ユーザーに対してリクエストしているデータのリストが提示され、承認を求められます。

    authorize:withHandler:への2つ目のパラメーターは、AMZNAuthorizationRequestHandlerで、これについては次のステップで説明します(コードのサンプルについては、ステップ3を参照してください)。

  3. AMZNAuthorizationRequestHandlerブロックオブジェクトを作成します。

    AMZNAuthorizationRequestHandler は、authorize:withHandler:呼び出しの結果を処理します。objective-cブロックの詳細については、developer.apple.comブロックの使用を参照してください。

    AMZNAuthorizationRequestHandlerの最初のパラメーターはAMZNAuthorizeResultオブジェクトです。ユーザーが正常に認可されたら、AMZNAuthorizeResultには、ユーザーのプロファイルデータにアクセスするために使用できるアクセストークンと、ユーザーのプロファイルデータを含むAMZNUserオブジェクトが含まれます。

    AMZNAuthorizationRequestHandlerの2つ目のパラメーターは、userDidCancelと呼ばれるブール値です。このパラメーターには、ユーザーが以下を実行した場合にtrueがセットされます。

    • ログインと認可の途中でSafari View Controllerを閉じた場合(iOS 9以上)
    • Amazonショッピングアプリ内でウェブ表示を閉じた場合
    • ログインをキャンセルし、認可を拒否した場合

    AMZNAuthorizationRequestHandlerの3つ目のパラメーターは、SDKまたは認可サーバーが原因でログインと認可が失敗した場合のエラーの詳細を含むNSErrorオブジェクトです。

    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. 製品に、認可コード、クライアントID、およびリダイレクトURIを安全に送信します。

  5. 製品が認可コード、クライアントID、およびリダイレクトURIを受信したら、製品はLWAを呼び出して、アクセストークンとリフレッシュトークンを取得するために認可コードを送信します。呼び出しを行うとき、製品はPOSTリクエストをhttps://api.amazon.com/auth/O2/tokenに送信し、以下のパラメーターを渡す必要があります:

    • grant_type: authorization_code
    • code: iOSアプリから取得した認可コード。
    • redirect_uri: iOSアプリから取得したリダイレクトURI。
    • client_id: iOSアプリから取得したクライアントID。
    • code_verifier: 製品で最初に生成されたコードベリファイア。

    応答には、以下の値が含まれます。

    • access_token: アクセストークン。
    • refresh_token: リフレッシュトークン。
    • token_type: ベアラー。
    • expires_in: アクセストークンの有効期限が切れるまでの秒数。
  6. アクセストークンの有効期限が切れたか、間もなく切れるとき、新しいアクセストークンとリフレッシュトークンを取得するためにリフレッシュトークンを交換できます。そのためには、https://api.amazon.com/auth/O2/tokenにPOSTし、以下のパラメーターを送信します:

    • grant_type: refresh_token
    • refresh_token: リフレッシュトークン。
    • client_id: iOSアプリから取得したクライアントID。

    応答には、以下の値が含まれます。

    • access_token: アクセストークン。
    • refresh_token: リフレッシュトークン。
    • token_type: ベアラー。
    • expires_in: アクセストークンの有効期限が切れるまでの秒数。

リソース