透過 OAuth (Dialogflow) 連結帳戶

OAuth 連結類型支援兩種符合業界標準的 OAuth 2.0 流程,分別是隱含授權程式碼流程。

在隱式程式碼流程中,Google 會在使用者的瀏覽器中開啟您的授權端點。成功登入後,系統會將長期存取權杖傳回 Google。從現在起,每次透過 Google 助理傳送給您動作的要求中,都會包含這個存取權杖。

在授權碼流程中,您需要兩個端點:

  • 授權端點,該端點負責將登入 UI 提供給未登入的使用者,並以簡碼授權代碼的形式,記錄使用者要求的存取權。
  • 權杖交換端點,負責以下兩種交換類型:
    1. 交換長期更新權杖的授權碼和短期存取權杖。這項交換作業會在使用者完成帳戶連結流程時進行。
    2. 對短期存取權杖交換交換憑證。當 Google 需要新的存取權杖,因為更新權杖已過期時,就會發生此交換行為。

雖然隱含程式碼流程的實作方式較簡單,但 Google 建議使用隱含流程發布的存取權杖不會過期,因為若權杖與隱含流程搭配使用,就會強制使用者重新連結帳戶。如果基於安全考量而需要權杖過期,您應該考慮改用授權碼流程。

實作 OAuth 帳戶連結

設定專案

如要將專案設為使用 OAuth 帳戶連結功能,請按照下列步驟操作:

  1. 開啟「Actions Console」,然後選取要使用的專案。
  2. 按一下「開發」分頁標籤,然後選擇「帳戶連結」
  3. 啟用「帳戶連結」旁的切換鈕。
  4. 在「建立帳戶」部分中,選取「否,我只想允許在我的網站上建立帳戶」

  5. 在「連結類型」中,選取「OAuth」OAuth和「隱含」OAuth

  6. 在「客戶資訊」部分中:

    • 將值指派給「Actions to Google」的用戶端 ID,即可識別來自 Google 的要求。
    • 插入授權和權杖交換端點的網址。
  1. 點按「儲存」

實作 OAuth 伺服器

為了支援 OAuth 2.0 隱含流程,您的服務會透過 HTTPS 提供授權端點。這個端點會負責驗證及取得使用者的同意聲明,取得資料存取權。授權端點代表尚未登入的使用者登入使用者介面,並記錄對於要求存取權的同意。

當您的動作需要呼叫服務的其中一個已授權 API 時,Google 會使用此端點取得使用者授權,讓他們代表他們呼叫這些 API。

Google 發起的一般 OAuth 2.0 隱含流程工作階段如下:

  1. Google 會在使用者的瀏覽器中開啟授權端點。如果使用者尚未登入,則請使用者登入;如果他們尚未授予權限,則授權 Google 使用您的 API 存取他們的資料。
  2. 您的服務會建立存取權杖,並透過將使用者的瀏覽器重新導向回 Google,使其傳回附加在要求中的存取權杖,藉此將權杖傳回 Google。
  3. Google 會呼叫服務的 API,並在每個要求中附加存取權杖。您的服務會驗證存取權杖是否已授予 Google 存取 API 的授權,然後完成 API 呼叫。

處理授權要求

當您的動作需要透過 OAuth2 隱含流程執行帳戶連結時,Google 會透過含有下列參數的要求,將使用者導向您的授權端點:

授權端點參數
client_id 您指派給 Google 的用戶端 ID。
redirect_uri 您將回應傳送至此要求的網址。
state 傳遞至 Google 的簿記值在重新導向 URI 中維持不變。
response_type 回應中要傳回的值類型。若為 OAuth 2.0 隱含流程,回應類型一律為 token

舉例來說,如果您在 https://myservice.example.com/auth 取得授權端點,要求看起來可能會像這樣:

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

如要讓授權端點處理登入要求,請按照下列步驟操作:

  1. 驗證 client_idredirect_uri 值,避免將存取權授予未預期或設定錯誤的用戶端應用程式:

    • 確認 client_id 與您指派給 Google 的用戶端 ID 相符。
    • 確認 redirect_uri 參數指定的網址形式如下:
      https://oauth-redirect.googleusercontent.com/r/YOUR_PROJECT_ID
      YOUR_PROJECT_ID 是 Actions 主控台「Project settings」(專案設定) 頁面上的 ID。
  2. 檢查使用者是否已登入您的服務。如果使用者尚未登入,請完成服務的登入或註冊流程。

  3. 產生 Google 用來存取 API 的存取權杖。存取權杖可以是任何字串值,但權杖必須專門代表使用者和用戶端,且不得猜測。

  4. 傳送 HTTP 回應,將使用者的瀏覽器重新導向至 redirect_uri 參數指定的網址。在網址片段中加入下列所有參數:

    • access_token:您剛剛產生的存取權杖
    • token_type:字串 bearer
    • state:原始要求中的未修改狀態值。以下是結果網址示例:
      https://oauth-redirect.googleusercontent.com/r/YOUR_PROJECT_ID#access_token=ACCESS_TOKEN&token_type=bearer&state=STATE_STRING

Google 的 OAuth 2.0 重新導向處理常式會收到存取權杖,並確認 state 值並未變更。Google 取得服務的存取權杖之後,Google 會透過 AppRequest,將權杖附加至後續的呼叫動作。

啟動驗證流程

使用帳戶登入輔助程式意圖啟動驗證流程。下列程式碼片段說明如何在 Dialogflow 和 Actions SDK 中傳送回應,以使用此輔助程式。

Dialogflow:

Node.js
const {dialogflow, SignIn} = require('actions-on-google');
const app = dialogflow({
  // REPLACE THE PLACEHOLDER WITH THE CLIENT_ID OF YOUR ACTIONS PROJECT
  clientId: CLIENT_ID,
});
// Intent that starts the account linking flow.
app.intent('Start Signin', (conv) => {
  conv.ask(new SignIn('To get your account details'));
});
Java
@ForIntent("Start Signin")
public ActionResponse text(ActionRequest request) {
  ResponseBuilder rb = getResponseBuilder(request);
  return rb.add(new SignIn().setContext("To get your account details")).build();
}
JSON
{
  "payload": {
    "google": {
      "expectUserResponse": true,
      "richResponse": {
        "items": [
          {
            "simpleResponse": {
              "textToSpeech": "PLACEHOLDER"
            }
          }
        ]
      },
      "userStorage": "{\"data\":{}}",
      "systemIntent": {
        "intent": "actions.intent.SIGN_IN",
        "data": {
          "@type": "type.googleapis.com/google.actions.v2.SignInValueSpec",
          "optContext": "To get your account details"
        }
      }
    }
  },
  "outputContexts": [
    {
      "name": "/contexts/_actions_on_google",
      "lifespanCount": 99,
      "parameters": {
        "data": "{}"
      }
    }
  ]
}

Actions SDK:

Node.js
const {actionssdk, SignIn} = require('actions-on-google');
const app = actionssdk({
  // REPLACE THE PLACEHOLDER WITH THE CLIENT_ID OF YOUR ACTIONS PROJECT
  clientId: CLIENT_ID,
});
// Intent that starts the account linking flow.
app.intent('actions.intent.TEXT', (conv) => {
  conv.ask(new SignIn('To get your account details'));
});
Java
@ForIntent("actions.intent.TEXT")
public ActionResponse text(ActionRequest request) {
  ResponseBuilder rb = getResponseBuilder(request);
  return rb.add(new SignIn().setContext("To get your account details")).build();
}
JSON
{
  "expectUserResponse": true,
  "expectedInputs": [
    {
      "inputPrompt": {
        "richInitialPrompt": {
          "items": [
            {
              "simpleResponse": {
                "textToSpeech": "PLACEHOLDER"
              }
            }
          ]
        }
      },
      "possibleIntents": [
        {
          "intent": "actions.intent.SIGN_IN",
          "inputValueData": {
            "@type": "type.googleapis.com/google.actions.v2.SignInValueSpec",
            "optContext": "To get your account details"
          }
        }
      ]
    }
  ],
  "conversationToken": "{\"data\":{}}",
  "userStorage": "{\"data\":{}}"
}

處理資料存取要求

如果 Google 助理要求包含存取權杖,請先檢查存取權杖是否有效 (且未過期),然後從資料庫中擷取相關聯的使用者帳戶。