OAuth 2.0 サーバーを実装する

すべての Cloud-to-cloud インテグレーションには、ユーザーを認証するための仕組みを組み込む必要があります。

認証を使用すると、ユーザーの Google アカウントと認証システム内のユーザー アカウントを関連付けることができます。これにより、フルフィルメントでスマートホーム インテントを受け取ったときにユーザーを識別できます。Google スマートホームは、認可コードフローを使用した OAuth のみに対応します。

このページでは、OAuth 2.0 サーバーを設定して Cloud-to-cloud インテグレーションと連携させる方法について説明します。

OAuth による Google アカウントのリンク

認証コードフローでは、次の 2 つのエンドポイントが必要になります。

  • 認可エンドポイント。ログインしていないユーザーにログイン用の UI を表示します。認可エンドポイントは、リクエストされたアクセスに対するユーザーの同意を記録するための短期間の認証コードも作成します。

  • トークン交換エンドポイント。次の 2 種類の交換を処理します。

    1. 長期の更新トークンと短期のアクセス トークンの認可コードを交換します。この処理は、ユーザーがアカウントのリンクフローを行ったときに発生します。
    2. 長期の更新トークンを短期のアクセス トークンと交換します。この処理は、トークンが期限切れになり、新しいアクセス トークンが必要になった場合に発生します。

設計ガイドライン

このセクションでは、OAuth リンクフロー用にホストするユーザー画面の設計要件と推奨事項について説明します。Google のアプリから呼び出されると、プラットフォームは Google へのログインページとアカウントのリンクに関する同意画面をユーザーに表示します。ユーザーがアカウントのリンクに同意すると、Google のアプリにリダイレクトされます。

この図は、ユーザーが自身の Google アカウントをデベロッパーの認証システムにリンクする手順を示しています。1 つ目のスクリーンショットは、プラットフォームからユーザーが開始したリンクを示しています。2 番目の画像は Google へのユーザーのログインを示し、3 番目の画像はユーザーの同意と Google アカウントをアプリにリンクするための確認を示しています。最後のスクリーンショットは、Google アプリでユーザー アカウントが正常にリンクされたことを示しています。
図 1. アカウント リンクのユーザーの Google へのログインと同意画面。

要件

  1. ユーザーのアカウントは Google にリンクされ、Google Home や Google アシスタントなどの特定の Google サービスにはリンクされないことを伝える必要があります。
  2. 「ログインすると、Google がデバイスを操作することを承認したことになります。」などの Google 承認文言が必要です。Google Home デベロッパー ポリシーの Google デバイス制御の承認に関するセクションをご覧ください。
  3. ウェブ OAuth リンクページを開き、ユーザーが Google アカウントにログインするための明確な方法(ユーザー名とパスワードのフィールドなど)があることを確認する必要があります。ユーザーがウェブ OAuth リンクページに移動せずにリンクできる Google ログイン(GSI)メソッドは使用しないでください。これは Google のポリシー違反に該当します。
  4. ユーザーがリンクしている統合を示すため、OAuth リンクページに次の項目のうち少なくとも 1 つを含める必要があります。
    • 会社のロゴ
    • 会社名
    • 連携名
    • アプリのアイコン

推奨事項

次のことをおすすめします。

  1. Google のプライバシー ポリシーを表示します。同意画面に Google のプライバシー ポリシーへのリンクを含めます。

  2. 共有されるデータ。Google がユーザーのどのデータを必要とするか、その理由、Google がデベロッパーと共有する可能性のある使用状況やインタラクションのデータについて、明確かつ簡潔な言葉でユーザーに伝えます。

  3. 行動を促す明確なフレーズがある。同意画面に「同意してリンク」などの明確な行動を促すフレーズを記載します。これは、ユーザーがアカウントをリンクするために Google と共有する必要があるデータを理解する必要があるためです。

  4. 解約できること。リンクしないことを選択した場合に、ユーザーが戻ったりキャンセルしたりできる方法を提供します。

  5. 明確なログイン プロセス。ユーザー名とパスワードの入力欄や [Google でログイン] など、ユーザーが Google アカウントにログインするための明確な方法を用意してください。

  6. リンクを解除できること。ユーザーがリンクを解除できる仕組み(プラットフォームのアカウント設定への URL など)を提供します。または、ユーザーがリンクされたアカウントを管理できる Google アカウントへのリンクを含めることもできます。ユーザーが統合からリンクを解除した場合は、agentUsers.delete を使用して Google に変更を通知します。

  7. ユーザー アカウントを変更する機能。ユーザーがアカウントを切り替える方法を提案します。これは、ユーザーが複数のアカウントを持っている場合に特に便利です。

    • アカウントを切り替えるために同意画面を閉じる必要がある場合は、回復可能なエラーを Google に送信して、ユーザーが OAuth リンクを使用して目的のアカウントにログインできるようにします。
  8. ロゴを含めます。同意画面に会社のロゴを表示します。スタイル ガイドラインに沿ってロゴを配置します。Google のロゴも表示する場合は、ロゴと商標をご覧ください。

認可コードフロー

認可コードフローの OAuth 2.0 サーバー実装は、サービスが HTTPS で利用できるようにする 2 つのエンドポイントで構成されます。最初のエンドポイントは認可エンドポイントです。これは、データアクセスに対するユーザーの同意を検索または取得します。認可エンドポイントは、まだログインしていないユーザーにログイン UI を表示し、リクエストされたアクセスに対する同意を記録します。2 つ目のエンドポイントはトークン交換エンドポイントです。これは、ユーザーがサービスにアクセスすることを認可するトークンと呼ばれる暗号化された文字列を取得するために使用されます。

Google アプリケーションがサービスの API のいずれかを呼び出す必要がある場合、Google はこれらのエンドポイントを組み合わせて使用し、ユーザーに代わってこれらの API を呼び出す許可をユーザーから取得します。

Google によって開始された OAuth 2.0 認可コードフロー セッションのフローは次のとおりです。

  1. Google がユーザーのブラウザで認可エンドポイントを開きます。アクションのフローが音声のみのデバイスで開始された場合、Google は実行をスマートフォンに転送します。
  2. ユーザーがログインしていない場合はログインし、API を使用してデータにアクセスする権限を Google に付与していない場合は付与します。
  3. サービスが認証コードを作成し、Google に返します。これを行うには、リクエストに認可コードを添付して、ユーザーのブラウザを Google にリダイレクトします。
  4. Google は認可コードをトークン交換エンドポイントに送信します。このエンドポイントは、コードの信頼性を検証し、アクセス トークンと更新トークンを返します。アクセス トークンは、API にアクセスするための認証情報としてサービスが受け入れる有効期間の短いトークンです。更新トークンは、Google が保存して、アクセス トークンの有効期限が切れたときに新しいアクセス トークンを取得するために使用できる、有効期間の長いトークンです。
  5. ユーザーがアカウントのリンクフローを完了すると、Google から送信される後続のすべてのリクエストにアクセス トークンが含まれます。

認可リクエストの処理

OAuth 2.0 認可コードフローを使用してアカウント リンクを行う必要がある場合、Google は次のパラメータを含むリクエストをユーザーの認可エンドポイントに送信します。

認可エンドポイントのパラメータ
client_id Google に割り当てたクライアント ID。
redirect_uri このリクエストに対するレスポンスを送信する宛先 URL。
state リダイレクト URL で変更されずに Google に返される会計上の値。
scope 省略可: Google が承認をリクエストしているデータを指定する、スペース区切りのスコープ文字列のセット。
response_type レスポンスで返される値のタイプ。OAuth 2.0 認可コードフローの場合、レスポンス タイプは常に code です。

たとえば、認可エンドポイントが https://myservice.example.com/auth で利用可能な場合、リクエストは次のようになります。

GET https://myservice.example.com/auth?client_id=GOOGLE_CLIENT_ID&redirect_uri=REDIRECT_URI&state=STATE_STRING&scope=REQUESTED_SCOPES&response_type=code

認可エンドポイントでログイン リクエストを処理する手順は次のとおりです。

  1. client_id が Google に割り当てたクライアント ID と一致し、redirect_uri が Google から提供されたサービスのリダイレクト URL と一致していることを確認します。これらのチェックは、意図しないクライアント アプリや構成が不適切なクライアント アプリにアクセス権を付与しないようにするために重要です。複数の OAuth 2.0 フローをサポートしている場合は、response_type が code であることも確認します。
  2. ユーザーがサービスにログインしているかどうか確認します。ユーザーがログインしていない場合は、サービスのログインまたは登録フローを完了します。
  3. Google が API へのアクセスに使用する認証コードを生成します。認証コードは任意の文字列値にできますが、ユーザー、トークンの対象となるクライアント、コードの有効期限を一意に表す必要があります。また、推測できないようにする必要があります。通常、約 10 分後に期限切れになる認可コードを発行します。
  4. redirect_uri パラメータで指定された URL が次の形式であることを確認します。
      https://oauth-redirect.googleusercontent.com/r/YOUR_PROJECT_ID
      https://oauth-redirect-sandbox.googleusercontent.com/r/YOUR_PROJECT_ID
      
  5. ユーザーのブラウザを redirect_uri パラメータで指定された URL にリダイレクトします。code パラメータと state パラメータを追加してリダイレクトするときに、生成した認証コードと、変更されていない元の状態値を含めます。結果の URL の例を次に示します。
    https://oauth-redirect.googleusercontent.com/r/YOUR_PROJECT_ID?code=AUTHORIZATION_CODE&state=STATE_STRING

トークン交換リクエストの処理

サービスのトークン交換エンドポイントは、次の 2 種類のトークン交換を処理します。

  • 認可コードとアクセス トークンおよび更新トークンとの交換
  • 更新トークンとアクセス トークンの交換

トークン交換リクエストには、次のパラメータを使用します。

トークン交換エンドポイントのパラメータ
client_id リクエスト元を Google として識別する文字列。この文字列は、Google の一意の識別子としてシステムに登録されている必要があります。
client_secret Google に登録したサービスのシークレット文字列。
grant_type 交換されるトークンの種類。authorization_code または refresh_token のいずれかです。
code grant_type=authorization_code の場合、このパラメータは、Google がログイン エンドポイントまたはトークン交換エンドポイントから受信したコードです。
redirect_uri grant_type=authorization_code の場合、このパラメータは最初の認可リクエストで使用される URL です。
refresh_token grant_type=refresh_token の場合、このパラメータは、Google がトークン交換エンドポイントから受信した更新トークンです。

Google がサーバーに認証情報を送信する方法を構成する

実装に応じて、認可サーバーはリクエスト本文またはリクエスト ヘッダーでクライアント認証情報を受け取ることを想定しています。

デフォルトでは、Google はリクエスト本文で認証情報を送信します。認可サーバーでクライアント認証情報がリクエスト ヘッダーに含まれている必要がある場合は、それに応じて Cloud-to-cloud 統合を構成する必要があります。

Developer Console に移動

  1. プロジェクトのリストで、作業するプロジェクトの横にある [開く] をクリックします。

  2. [Cloud-to-Cloud] で [開発] を選択します。

  3. 統合の横にある [開く] をクリックします。

  4. [権限(省略可)] セクションまで下にスクロールし、[Google に HTTP 基本認証ヘッダーを介してクライアント ID とシークレットを送信させる] チェックボックスをオンにします。

  5. [保存] をクリックして、変更を保存します。

認可コードとアクセス トークンおよび更新トークンとの交換

ユーザーがログインし、認可エンドポイントが有効期間の短い認可コードを Google に返すと、Google はトークン交換エンドポイントにリクエストを送信して、認可コードをアクセス トークンと更新トークンに交換します。

これらのリクエストでは、grant_type の値は authorization_code で、code の値は、以前に Google に付与した認証コードの値です。以下は、認証コードをアクセス トークンと更新トークンに交換するリクエストの例です。

POST /token HTTP/1.1
Host: oauth2.example.com
Content-Type: application/x-www-form-urlencoded

client_id=GOOGLE_CLIENT_ID&client_secret=GOOGLE_CLIENT_SECRET&grant_type=authorization_code&code=AUTHORIZATION_CODE&redirect_uri=REDIRECT_URI

認証コードをアクセス トークンと更新トークンに交換するには、トークン交換エンドポイントが次の手順を実行して POST リクエストに応答します。

  1. client_id がリクエストの送信元を承認済みの送信元として識別し、client_secret が想定される値と一致していることを確認します。
  2. 認証コードが有効で、期限切れになっていないこと、リクエストで指定されたクライアント ID が認証コードに関連付けられているクライアント ID と一致していることを確認します。
  3. redirect_uri パラメータで指定された URL が、最初の認可リクエストで使用された値と同じであることを確認します。
  4. 上記のすべての条件を確認できない場合は、本文に {"error": "invalid_grant"} を含む HTTP 400 Bad Request エラーを返します。
  5. それ以外の場合は、認可コードのユーザー ID を使用して、更新トークンとアクセス トークンを生成します。これらのトークンは任意の文字列値にできますが、トークンの対象となるユーザーとクライアントを一意に表す必要があり、推測できないようにする必要があります。アクセス トークンの場合は、トークンの有効期限も記録します。通常、トークンの発行から 1 時間後です。更新トークンに有効期限はありません。
  6. HTTPS レスポンスの本文で次の JSON オブジェクトを返します。
    {
    "token_type": "Bearer",
    "access_token": "ACCESS_TOKEN",
    "refresh_token": "REFRESH_TOKEN",
    "expires_in": SECONDS_TO_EXPIRATION
    }

Google は、ユーザーのアクセス トークンと更新トークンを保存し、アクセス トークンの有効期限を記録します。アクセス トークンの有効期限が切れると、Google は更新トークンを使用して、トークン交換エンドポイントから新しいアクセス トークンを取得します。

更新トークンとアクセス トークンの交換

アクセス トークンの有効期限が切れると、Google はトークン交換エンドポイントにリクエストを送信して、更新トークンを新しいアクセス トークンと交換します。

これらのリクエストでは、grant_type の値は refresh_token で、refresh_token の値は以前に Google に付与した更新トークンの値です。更新トークンをアクセス トークンと交換するリクエストの例を次に示します。

POST /token HTTP/1.1
Host: oauth2.example.com
Content-Type: application/x-www-form-urlencoded

client_id=GOOGLE_CLIENT_ID&client_secret=GOOGLE_CLIENT_SECRET&grant_type=refresh_token&refresh_token=REFRESH_TOKEN

更新トークンをアクセス トークンと交換するには、トークン交換エンドポイントが次の手順を実行して POST リクエストに応答します。

  1. client_id でリクエストの送信元が Google であることを確認し、client_secret が想定される値と一致していることを確認します。
  2. 更新トークンが有効で、リクエストで指定されたクライアント ID が更新に関連付けられたクライアント ID と一致していることを確認します。
  3. 上記のすべての条件を確認できない場合は、本文に {"error": "invalid_grant"} を含む HTTP 400 Bad Request エラーを返します。
  4. それ以外の場合は、更新トークンのユーザー ID を使用してアクセス トークンを生成します。これらのトークンは任意の文字列値にできますが、トークンの対象となるユーザーとクライアントを一意に表す必要があり、推測できないようにする必要があります。アクセス トークンの場合は、トークンの有効期限も記録します。通常は、トークンを発行してから 1 時間後です。
  5. HTTPS レスポンスの本文で次の JSON オブジェクトを返します。
    {
    "token_type": "Bearer",
    "access_token": "ACCESS_TOKEN",
    "expires_in": SECONDS_TO_EXPIRATION
    }

userinfo リクエストを処理する

userinfo エンドポイントは、OAuth 2.0 で保護されたリソースで、リンクされたユーザーに関するクレームを返します。userinfo エンドポイントの実装とホストは任意ですが、以下のユースケースを除きます。

トークン エンドポイントからアクセス トークンが正常に取得されると、Google は、リンクされたユーザーに関する基本的なプロフィール情報を取得するためのリクエストを userinfo エンドポイントに送信します。

userinfo エンドポイント リクエスト ヘッダー
Authorization header Bearer タイプのアクセス トークン。

たとえば、userinfo エンドポイントが https://myservice.example.com/userinfo の場合、リクエストは次のようになります。

GET /userinfo HTTP/1.1
Host: myservice.example.com
Authorization: Bearer ACCESS_TOKEN

userinfo エンドポイントでリクエストを処理するには、次の手順を行います。

  1. Authorization ヘッダーからアクセス トークンを抽出し、そのアクセス トークンに関連付けられたユーザーの情報を返します。
  2. アクセス トークンが無効な場合は、WWW-Authenticate レスポンス ヘッダーを使用して HTTP 401 Unauthorized エラーを返します。userinfo エラー レスポンスの例を次に示します。
    HTTP/1.1 401 Unauthorized
    WWW-Authenticate: error="invalid_token",
    error_description="The Access Token expired"
    
    リンク処理中に 401 Unauthorized またはその他の失敗したエラー レスポンスが返された場合、そのエラーは修復不能となり、取得したトークンは破棄されるため、ユーザーはリンク処理をやり直す必要があります。
  3. アクセス トークンが有効な場合は、HTTPS の本文に次の JSON オブジェクトを含む HTTP 200 レスポンスを返します。 レスポンス:

    {
    "sub": "USER_UUID",
    "email": "EMAIL_ADDRESS",
    "given_name": "FIRST_NAME",
    "family_name": "LAST_NAME",
    "name": "FULL_NAME",
    "picture": "PROFILE_PICTURE",
    }
    userinfo エンドポイントが HTTP 200 成功レスポンスを返すと、取得したトークンとクレームがユーザーの Google アカウントに登録されます。

    userinfo エンドポイント レスポンス
    sub システム内でユーザーを識別する一意の ID。
    email ユーザーのメールアドレス。
    given_name 省略可: ユーザーの名。
    family_name 省略可: ユーザーの姓。
    name 省略可: ユーザーの氏名。
    picture 省略可: ユーザーのプロフィール写真。