[日本語Alexa] APIGatewayをプロキシーとして設定し、アカウントリンクをデバッグする

1 はじめに

Alexaのスキル開発では、他のシステムの機能を利用するためには、アカウントリンクが、ほぼ必須と言っても過言では無いと思います。

しかし、アカウントリンクの設定は、結構ややこしくて、下記のような画面を見て悲しい思いをした方は多いのでは無いでしょうか。

アカウントリンクが、うまく動作しない時の原因追求は、結構厄介です。その理由は、まず、エラーの原因を確認しようにもAlexa側のログを見ることが出来ないからです。また、OAuth2側も自前で提供していない限り、その詳細な情報を確認することは難しいでしょう。

結果的に、設定値を色々変えて、ひたすら試行錯誤するような作業になってしまいます。

今回は、Alexaブログで紹介されていた下記の記事を元に、アカウントリンクをデバッグする手法について試してみました。


How to Set up Amazon API Gateway as a Proxy to Debug Account Linking

2 HTTPプロキシー

先のBlogでは、Alexa Skill ServiceとOAuth2の間でAmazon API GatewayによるHTTPプロキシを設定し、すべてのHTTPトラフィックをログに記録する方法について紹介されています。


How to Set up Amazon API Gateway as a Proxy to Debug Account Linkings より

同ブログでは、仕組みについて紹介するだけでなく、CloudFormationのテンプレートも提供されているため、非常に簡単にこのデバッグ用環境を構築することが可能です。

3 環境構築

それでは、簡単に環境構築の手順をなぞってみます。(詳しくは、本家ブログをご参照下さい)

  • CloudFormationでスタックを作成

2 Choose FileからBlogで提供されているcloudformation-us-east-1.jsonを選択(バージニア及びアイルランド用が提供されています)

  • 適当な名前を設定し、認証URLとトークンURLをセットする

※認証URLとトークンURLは、デフォルトでLogin with Amazon のものが設定されていますが、対象となるOAuth2のものに合わせる必要が有ります。

  • AWS CloudFormationによってIAMリソースが作成される場合があることを承認する にチェックを入れます。

  • CREATE COMPLETEとなったら構築完了

  • 出力タブで作成された2つのURLをコピーする

ここで出力される認証URLとトークンURLが、プロキシーの入口となるため、Alexa開発者コンソールのアカウントリンクの設定でURLをこれに置き換えることで、アクセスがプロキシー経由となります。

どちらか1つだけ設定して、認可URLだけや、トークンURLだけをプロキシーすることも可能です。

ProxyAccessTokenURL
https://v0lxlti7a0.execute-api.us-east-1.amazonaws.com/DebugStage/token

ProxyAuthenticationURL
https://v0lxlti7a0.execute-api.us-east-1.amazonaws.com/DebugStage

4 ログ

CloudWatchのLogsに3つの新しいロググループが表示されています。

この中で、プロキシーとしてのログは、API-Gateway-Execution-Log.*グループにあります。

このグループには多数のストリームがありますが、ここにログが出力され、日付でソートすることで、最新のもの確認することが出来ます。

下記は、一度、アカウントリンクを実行した後の状態ですが、2つのログが出力されていることを確認できます。1つは、認証サーバに対するもので、もう一つは、アクセストークンを取得する時のログです。 当然ですが、認可に関するのアクセス(認証画面でユーザ・パスワード・認可画面で権限を許可)のトラフィックは、このプロキシーを通りませんので、モニターすることはできません。

下記は、ログから主要な情報を抜き出してみたものです。

(1) 認証サーバへのアクセス

> Sending request
https://www.amazon.com/ap/oa?response_type=code&redirect_uri=https://pitangui.amazon.com/api/skill/link/M3H1X8EM96OU2C&state=xxxx&client_id=amzn1.application-oa2-client.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&scope=profile

> Received response. Integration latency: 67 ms
> Endpoint response body before transformations:
> Method completed with status: 302

(2) アクセストークン取得のアクセス

> Sending request
https://api.amazon.com/auth/o2/token
grant_type=authorization_code&code=xxxxxxxx&redirect_uri=https%3A%2F%2Fpitangui.amazon.com%2Fapi%2Fskill%2Flink%2FM3H1X8EM96OU2C&client_id=amzn1.application-oa2-client.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

headers: 
{
    // 略
    Authorization=********diNjMy,
    // 略
}


> Received response. Integration latency: 39 ms
> Endpoint response body before transformations: 
{
    "access_token":"Atza|xxxxxxxx",
    "refresh_token":"Atzr|xxxxxxxx"
}
> Method completed with status: 200

※省略しましたが、各トラフィックのヘッダも確認できます。

ログから、リクエストのパス、クエリ文字列、ヘッダ、本文、そしてレスポンスを見ることができます。この情報から、設定した内容に誤りは無いか、また、どの段階でエラーとなっているのかなどを確認する事ができます。

5 試してみる

(1) 認証スキーム

アカウントリンクのクライアント認証スキームの設定で、「HTTPベーシック認証(推奨)」と「リクエストボディーの資格情報」が選択できるようになっていますが、この違いをログから比べてみましょう。

違いは、アクセストークンを取得するリクエストでクライアントシークレットをどうやってサーバに渡すかに現れます。HTTPベーシック認証では、ヘッダの Authorization= に置かれ、リクエストボディーの資格情報では、bodyの中に client_secret= として置かれています。

  • HTTPベーシック認証
> Sending request
https://api.amazon.com/auth/o2/token
grant_type=authorization_code&code=xxxxxxxx&redirect_uri=https%3A%2F%2Fpitangui.amazon.com%2Fapi%2Fskill%2Flink%2FM3H1X8EM96OU2C&client_id=amzn1.application-oa2-client.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

headers: 
{
    // 略
    Authorization=********diNjMy,
    // 略
}
  • リクエストボディーの資格情報
> Sending request
https://api.amazon.com/auth/o2/token
grant_type=authorization_code&code=xxxxxxxx&redirect_uri=https%3A%2F%2Fpitangui.amazon.com%2Fapi%2Fskill%2Flink%2FM3H1X8EM96OU2C&client_id=amzn1.application-oa2-client.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&client_secret=832edxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxd8e5c8477b632

(2) クライアントシークレットの誤り

クライアントシークレットに誤りがあった場合、いつものように原因のよく見えない画面になります。

しかし、ログを確認するとアクセストークンの取得時に認証失敗のログを確認できます。

Sending request to https://api.amazon.com/auth/o2/token

Endpoint response body before transformations:
{
    "error_description": "Client authentication failed",
    "error": "invalid_client"
}

(3) scopeの誤り

無効なscopeを設定してしまった場合の画面です。

こちらは、認証サーバからのレスポンスで、エラーの原因が書かれたリダイレクト先へ飛ばされてるのが分かります。「error_description=An+unknown+scope+was+requested

> Method response headers: {Strict-Transport-Security=max-age=47474747; includeSubDomains; preload, x-ua-compatible=IE=edge, Server=Server, Connection=keep-alive, Vary=Accept-Encoding,User-Agent, Set-Cookie=session-id-time=2148945636l; Domain=.amazon.com; Expires=Fri, 05-Feb-2038 01:20:36 GMT; Path=/, Content-Length=0, p3p=policyref="http://www.amazon.com/w3c/p3p.xml",CP="CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC ", Date=Sat, 10 Feb 2018 01:20:36 GMT, Content-Type=text/html;charset=UTF-8, Location=https://pitangui.amazon.com/api/skill/link/M3H1X8EM96OU2C?error_description=An+unknown+scope+was+requested&state=xxxxx [TRUNCATED]
> Successfully completed execution
> Method completed with status: 302

6 最後に

今回は、ちょっと厄介なアカウントリンクのエラー時のデバッグについて試してみました。ひたすら試行錯誤するよりも、このようにログを中心に追いかけた方が、きっと解決は早いでしょう。

なお、セキュリティ上の問題になりかねませんので、このようなプロキシーは、デバッグが終わったら確実に削除しておきましょう。CloudFormationのコンソールからスタックを削除することで,ログ以外は、全部削除されます。