实现 OAuth 2.0 服务器

每个 smart home 操作都必须包含身份验证机制。

通过身份验证,您可以将用户的 Google 账号与您的身份验证系统中的用户账号相关联。这样,您就可以在您的执行方式收到智能家居 intent 时识别用户。Google 智能家居仅支持包含授权代码流程的 OAuth。

本页介绍了如何设置 OAuth 2.0 服务器,以使其可与 smart home 操作搭配使用。

通过 OAuth 建立 Google 账号关联

授权代码流程中,您需要两个端点:

  • 授权端点,向尚未登录的用户显示登录界面。授权端点还会创建一个短期有效的授权代码,用于记录用户对所请求的访问的同意情况。

  • 令牌交换端点,负责进行两种类型的交换:

    1. 将授权代码交换为长期刷新令牌和短期访问令牌。这种交换会在用户完成帐号关联流程后发生。
    2. 将长期刷新令牌交换成短期访问令牌。当 Google 因访问令牌已过期而需要新的访问令牌时,就会发生这种交换。

设计准则

本部分介绍了您为 OAuth 关联流程托管的用户屏幕的设计要求和建议。Google 应用调用该方法后,您的平台会向用户显示登录 Google 页面和帐号关联同意屏幕。用户在同意关联帐号后,会被重定向回 Google 的应用。

此图显示了用户将其 Google 帐号与您的身份验证系统相关联的步骤。第一个屏幕截图显示了用户从您的平台发起的关联。第二张图片显示了用户登录 Google 的情况,第三张图片显示了用户同意和确认将其 Google 帐号与您的应用相关联。最后的屏幕截图显示了 Google 应用中已成功关联的用户帐号。
图 1.帐号关联用户登录 Google 和权限请求页面。

要求

  1. 您必须表明用户的帐号将关联到 Google,而不是 Google Home 或 Google 助理等特定 Google 产品。
  2. 您必须拥有 Google 授权声明,例如“登录即表示您授权 Google 控制您的设备”。请参阅 Google Home 开发者政策的 Google 设备控制授权部分
  3. 如果用户选择不关联,您必须为他们提供返回或取消的途径。
  4. 您必须打开“网络 OAuth 关联”页面,并确保用户能够清楚使用方法(例如用户名和密码字段)登录其 Google 帐号。不要使用 Google 登录 (GSI) 方法,让用户不必转到“网络 OAuth 关联”页面就可以进行关联。这违反了 Google 政策。

建议

建议您执行以下操作:

  1. 显示 Google 的隐私权政策。在同意屏幕上添加指向 Google 隐私权政策的链接。

  2. 要分享的数据。使用简明扼要的语言告知用户 Google 需要他们的哪些数据以及原因。

  3. 明确的号召性用语。在您的同意屏幕上提供明确的号召性用语,例如“同意并关联”。这是因为用户需要了解他们需要与 Google 分享哪些数据才能关联帐号。

  4. 能够解除关联。提供一种供用户解除关联的机制,例如指向其在您平台上帐号设置的网址。或者,您也可以添加指向 Google 帐号的链接,以便用户管理其关联的帐号。

  5. 能够更改用户帐号。为用户提供切换帐号的方法。如果用户通常有多个帐号,此功能尤为有用。

    • 如果用户必须关闭同意屏幕才能切换帐号,请向 Google 发送可恢复的错误,以便用户通过 OAuth 关联方式登录所需的帐号。
  6. 添加徽标。在同意屏幕上显示贵公司的徽标。 根据样式准则放置徽标。如果您还想显示 Google 的徽标,请参阅徽标和商标

授权代码流程

An OAuth 2.0 server implementation of the authorization code flow consists of two endpoints, which your service makes available by HTTPS. The first endpoint is the authorization endpoint, which is responsible for finding or obtaining consent from users for data access. The authorization endpoint presents a sign-in UI to your users that aren't already signed in and records consent to the requested access. The second endpoint is the token exchange endpoint, which is used to obtain encrypted strings, called tokens, that authorize a user to access your service.

When a Google application needs to call one of your service's APIs, Google uses these endpoints together to get permission from your users to call these APIs on their behalf.

An OAuth 2.0 authorization code flow session initiated by Google has the following flow:

  1. Google opens your authorization endpoint in the user's browser. If the flow started on a voice-only device for an Action, Google transfers the execution to a phone.
  2. The user signs in, if not signed in already, and grants Google permission to access their data with your API, if they haven't already granted permission.
  3. Your service creates an authorization code and returns it to Google. To do so, redirect the user's browser back to Google with the authorization code attached to the request.
  4. Google sends the authorization code to your token exchange endpoint, which verifies the authenticity of the code and returns an access token and a refresh token. The access token is a short-lived token that your service accepts as credentials to access APIs. The refresh token is a long-lived token that Google can store and use to acquire new access tokens when they expire.
  5. After the user has completed the account linking flow, every subsequent request sent from Google contains an access token.

Handle authorization requests

When you need to perform account linking using the OAuth 2.0 authorization code flow, Google sends the user to your authorization endpoint with a request that includes the following parameters:

Authorization endpoint parameters
client_id The Client ID you assigned to Google.
redirect_uri The URL to which you send the response to this request.
state A bookkeeping value that is passed back to Google unchanged in the redirect URI.
scope Optional: A space-delimited set of scope strings that specify the data Google is requesting authorization for.
response_type The type of value to return in the response. For the OAuth 2.0 authorization code flow, the response type is always code.
user_locale The Google Account language setting in RFC5646 format, used to localize your content in the user's preferred language.

For example, if your authorization endpoint is available at https://myservice.example.com/auth, a request might look like the following:

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

For your authorization endpoint to handle sign-in requests, do the following steps:

  1. Verify that the client_id matches the Client ID you assigned to Google, and that the redirect_uri matches the redirect URL provided by Google for your service. These checks are important to prevent granting access to unintended or misconfigured client apps. If you support multiple OAuth 2.0 flows, also confirm that the response_type is code.
  2. Check if the user is signed in to your service. If the user isn't signed in, complete your service's sign-in or sign-up flow.
  3. Generate an authorization code for Google to use to access your API. The authorization code can be any string value, but it must uniquely represent the user, the client the token is for, and the code's expiration time, and it must not be guessable. You typically issue authorization codes that expire after approximately 10 minutes.
  4. Confirm that the URL specified by the redirect_uri parameter has the following form:
      https://oauth-redirect.googleusercontent.com/r/YOUR_PROJECT_ID
      https://oauth-redirect-sandbox.googleusercontent.com/r/YOUR_PROJECT_ID
      
  5. Redirect the user's browser to the URL specified by the redirect_uri parameter. Include the authorization code you just generated and the original, unmodified state value when you redirect by appending the code and state parameters. The following is an example of the resulting URL:
    https://oauth-redirect.googleusercontent.com/r/YOUR_PROJECT_ID?code=AUTHORIZATION_CODE&state=STATE_STRING

Handle token exchange requests

Your service's token exchange endpoint is responsible for two kinds of token exchanges:

  • Exchange authorization codes for access tokens and refresh tokens
  • Exchange refresh tokens for access tokens

Token exchange requests include the following parameters:

Token exchange endpoint parameters
client_id A string that identifies the request origin as Google. This string must be registered within your system as Google's unique identifier.
client_secret A secret string that you registered with Google for your service.
grant_type The type of token being exchanged. It's either authorization_code or refresh_token.
code When grant_type=authorization_code, this parameter is the code Google received from either your sign-in or token exchange endpoint.
redirect_uri When grant_type=authorization_code, this parameter is the URL used in the initial authorization request.
refresh_token When grant_type=refresh_token, this parameter is the refresh token Google received from your token exchange endpoint.

Exchange authorization codes for access tokens and refresh tokens

After the user signs in and your authorization endpoint returns a short-lived authorization code to Google, Google sends a request to your token exchange endpoint to exchange the authorization code for an access token and a refresh token.

For these requests, the value of grant_type is authorization_code, and the value of code is the value of the authorization code you previously granted to Google. The following is an example of a request to exchange an authorization code for an access token and a refresh token:

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

To exchange authorization codes for an access token and a refresh token, your token exchange endpoint responds to POST requests by executing the following steps:

  1. Verify that the client_id identifies the request origin as an authorized origin, and that the client_secret matches the expected value.
  2. Verify that the authorization code is valid and not expired, and that the client ID specified in the request matches the client ID associated with the authorization code.
  3. Confirm that the URL specified by the redirect_uri parameter is identical to the value used in the initial authorization request.
  4. If you can't verify all of the above criteria, return an HTTP 400 Bad Request error with {"error": "invalid_grant"} as the body.
  5. Otherwise, use the user ID from the authorization code to generate a refresh token and an access token. These tokens can be any string value, but they must uniquely represent the user and the client the token is for, and they must not be guessable. For access tokens, also record the expiration time of the token, which is typically an hour after you issue the token. Refresh tokens don't expire.
  6. Return the following JSON object in the body of the HTTPS response:
    {
    "token_type": "Bearer",
    "access_token": "ACCESS_TOKEN",
    "refresh_token": "REFRESH_TOKEN",
    "expires_in": SECONDS_TO_EXPIRATION
    }
    

Google stores the access token and the refresh token for the user and records the expiration of the access token. When the access token expires, Google uses the refresh token to get a new access token from your token exchange endpoint.

Exchange refresh tokens for access tokens

When an access token expires, Google sends a request to your token exchange endpoint to exchange a refresh token for a new access token.

For these requests, the value of grant_type is refresh_token, and the value of refresh_token is the value of the refresh token you previously granted to Google. The following is an example of a request to exchange a refresh token for an access token:

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

To exchange a refresh token for an access token, your token exchange endpoint responds to POST requests by executing the following steps:

  1. Verify that the client_id identifies the request origin as Google, and that the client_secret matches the expected value.
  2. Verify that the refresh token is valid, and that the client ID specified in the request matches the client ID associated with the refresh token.
  3. If you can't verify all of the above criteria, return an HTTP 400 Bad Request error with {"error": "invalid_grant"} as the body.
  4. Otherwise, use the user ID from the refresh token to generate an access token. These tokens can be any string value, but they must uniquely represent the user and the client the token is for, and they must not be guessable. For access tokens, also record the expiration time of the token, typically an hour after you issue the token.
  5. Return the following JSON object in the body of the HTTPS response:
    {
    "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. 从授权标头中提取访问令牌,并返回与访问令牌相关联的用户的信息。
  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 可选:用户的个人资料照片。