Device flow:使用 Logto 進行驗證 (Authentication)
本指南假設你已在 Logto Console 建立一個類型為「原生」且授權流程選擇 device flow 的應用程式。
簡介
OAuth 2.0 device authorization grant(device flow)專為輸入能力有限的裝置設計,例如智慧電視、遊戲主機、CLI 工具與 IoT 裝置。它允許使用者在裝置上啟動登入流程,但在另一台有瀏覽器的裝置(如手機或筆電)上完成驗證 (Authentication)。
由於裝置本身無法處理瀏覽器登入流程,裝置會顯示一組短碼與一個網址。使用者在另一台裝置上造訪該網址、輸入短碼並登入。此時,原始裝置會持續輪詢 Logto,直到授權完成。
取得應用程式憑證
在 Logto Console 中,前往你的應用程式詳細頁取得以下憑證:
- App ID:你的應用程式唯一識別碼(亦稱
client_id)。 - Logto endpoint:你的 Logto 授權伺服器端點,可於 Logto Console「應用程式詳細資訊」中找到。
若使用 Logto Cloud,端點為 https://{your-tenant-id}.logto.app。
Device flow 應用程式屬於公開用戶端,因此不需要 App Secret。
請求 device code
啟動 device flow,向 device authorization 端點發送 POST 請求:
curl --request POST 'https://your.logto.endpoint/oidc/device/auth' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=your-application-id' \
--data-urlencode 'scope=openid offline_access profile'
回應內容包含:
| 欄位 | 說明 |
|---|---|
device_code | 你的應用程式在輪詢權杖端點時使用的唯一代碼。 |
user_code | 顯示給使用者、讓其在瀏覽器輸入的短碼。 |
verification_uri | 使用者輸入 user_code 的網址。 |
verification_uri_complete | 已預填 user_code 的網址。使用者可直接造訪此網址以略過手動輸入——你可將其呈現為 QR code、可點擊連結等任意方式。 |
expires_in | device_code 與 user_code 的存活秒數。到期後請停止輪詢。 |
將驗證網址顯示給使用者
在裝置螢幕上顯示 user_code 與 verification_uri。
你也可以使用已預填短碼的 verification_uri_complete,使用者只需確認即可。呈現方式可自由選擇:QR code、可點擊連結等。
輪詢權杖
當使用者在瀏覽器完成驗證 (Authentication) 時,你的裝置應輪詢權杖端點。每次輪詢請至少間隔 5 秒:
curl --request POST 'https://your.logto.endpoint/oidc/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=your-application-id' \
--data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:device_code' \
--data-urlencode 'device_code=DEVICE_CODE'
將 DEVICE_CODE 替換為 device authorization 回應中的 device_code。
停止輪詢的時機:
- 收到成功的權杖回應。
- device code 回應中的
expires_in時間已過。 - 收到不可重試的錯誤(如
expired_token或access_denied)。
權杖回應
使用者授權後,回應內容包含:
| 欄位 | 說明 |
|---|---|
access_token | 存取權杖 (Access token)。預設為不透明權杖 (Opaque token);若有指定 resource,則為帶有 aud 為資源 URI 的 JWT。 |
id_token | 含有使用者身分宣告 (Claims) 的 ID 權杖 (ID token)。僅在請求 openid 權限範圍 (Scope) 時提供。 |
refresh_token | 用於無需重新驗證 (Authentication) 取得新權杖的重新整理權杖 (Refresh token)。僅在請求 offline_access 權限範圍時提供。 |
token_type | 一律為 Bearer。 |
expires_in | 權杖存活秒數。 |
scope | 授權伺服器核發的權限範圍 (Scopes)。 |
檢查點:測試你的 device flow
現在,測試你的 device flow 整合流程:
- 執行應用程式並觸發 device flow 以取得
device_code與user_code。 - 在瀏覽器開啟
verification_uri並輸入user_code,或直接使用verification_uri_complete省略手動輸入。 - 在瀏覽器完成登入流程。
- 確認應用程式輪詢後收到權杖。
取得使用者資訊
解碼 ID 權杖宣告 (Claims)
權杖回應中的 id_token 為標準 JSON Web Token (JWT)。你可以解碼 Base64URL 編碼的 payload(JWT 的第二段,以 . 分隔)來取得基本使用者宣告 (Claims),無需額外網路請求。
解碼後的 payload 會包含如 sub(使用者 ID)、name、email 等宣告,具體內容取決於請求的權限範圍 (Scopes)。
正式環境請務必驗證 JWT 簽章再信任其內容。可使用 Logto 端點的 JWKS(https://your.logto.endpoint/oidc/jwks)驗證權杖。
從 userinfo 端點取得
ID 權杖會根據請求的權限範圍 (Scopes) 帶有基本宣告 (Claims)。部分擴充宣告(如 custom_data、identities)僅能透過 OIDC UserInfo 端點 取得:
curl --request GET 'https://your.logto.endpoint/oidc/me' \
--header 'Authorization: Bearer ACCESS_TOKEN'
將 ACCESS_TOKEN 替換為權杖回應中取得的不透明權杖 (Opaque access token)(非 JWT 資源權杖)。回應為根據授權範圍 (Scopes) 的 JSON 使用者宣告 (Claims)。
請求額外宣告 (Claims)
你可能會發現 ID 權杖中缺少部分使用者資訊。這是因為 OAuth 2.0 與 OpenID Connect (OIDC) 遵循最小權限原則(PoLP),而 Logto 亦以此為基礎。
預設情況下,僅返回有限的宣告 (Claims)。如果你需要更多資訊,可以請求額外的權限範圍 (Scopes) 以存取更多宣告。
「宣告 (Claim)」是對主體所做的斷言;「權限範圍 (Scope)」是一組宣告。在目前的情況下,宣告是關於使用者的一部分資訊。
以下是權限範圍與宣告關係的非規範性範例:
「sub」宣告表示「主體 (Subject)」,即使用者的唯一識別符(例如使用者 ID)。
Logto SDK 將始終請求三個權限範圍:openid、profile 和 offline_access。
若需額外權限範圍 (Scopes),請在 device authorization 請求的 scope 參數中加入。例如請求使用者 email 與手機:
curl --request POST 'https://your.logto.endpoint/oidc/device/auth' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=your-application-id' \
--data-urlencode 'scope=openid offline_access profile email phone'
權限範圍 (Scopes) 與宣告 (Claims)
以下是支援的權限範圍 (Scopes) 及對應的宣告 (Claims) 清單:
標準 OIDC 權限範圍 (Scopes)
openid(預設)
| Claim name | Type | Description |
|---|---|---|
| sub | string | 使用者的唯一識別符 (The unique identifier of the user) |
profile(預設)
| Claim name | Type | Description |
|---|---|---|
| name | string | 使用者全名 (The full name of the user) |
| username | string | 使用者名稱 (The username of the user) |
| picture | string | 終端使用者大頭貼的 URL。此 URL 必須指向圖片檔案(如 PNG、JPEG 或 GIF),而非包含圖片的網頁。請注意,此 URL 應明確指向適合描述終端使用者的個人照片,而非任意由終端使用者拍攝的照片。(URL of the End-User's profile picture. This URL MUST refer to an image file (for example, a PNG, JPEG, or GIF image file), rather than to a Web page containing an image. Note that this URL SHOULD specifically reference a profile photo of the End-User suitable for displaying when describing the End-User, rather than an arbitrary photo taken by the End-User.) |
| created_at | number | 終端使用者建立時間。以自 Unix epoch(1970-01-01T00:00:00Z)以來的毫秒數表示。(Time the End-User was created. The time is represented as the number of milliseconds since the Unix epoch (1970-01-01T00:00:00Z).) |
| updated_at | number | 終端使用者資訊最後更新時間。以自 Unix epoch(1970-01-01T00:00:00Z)以來的毫秒數表示。(Time the End-User's information was last updated. The time is represented as the number of milliseconds since the Unix epoch (1970-01-01T00:00:00Z).) |
其他 標準宣告 (Standard claims) 包含 family_name、given_name、middle_name、nickname、preferred_username、profile、website、gender、birthdate、zoneinfo 及 locale 也會包含在 profile 權限範圍內,無需額外請求 userinfo endpoint。與上表宣告不同的是,這些宣告僅在其值不為空時才會回傳,而上表宣告若值為空則會回傳 null。
與標準宣告不同,created_at 與 updated_at 宣告使用毫秒而非秒為單位。
email
| Claim name | Type | Description |
|---|---|---|
string | 使用者的電子郵件地址 (The email address of the user) | |
| email_verified | boolean | 電子郵件地址是否已驗證 (Whether the email address has been verified) |
phone
| Claim name | Type | Description |
|---|---|---|
| phone_number | string | 使用者的電話號碼 (The phone number of the user) |
| phone_number_verified | boolean | 電話號碼是否已驗證 (Whether the phone number has been verified) |
address
請參閱 OpenID Connect Core 1.0 以瞭解 address 宣告的詳細資訊。
標註為 (預設) 的權限範圍 (Scopes) 會由 Logto SDK 自動請求。當請求對應權限範圍時,標準 OIDC 權限範圍下的宣告 (Claims) 會始終包含於 ID 權杖 (ID token) 中,且無法關閉。
擴充權限範圍 (Extended scopes)
以下權限範圍由 Logto 擴充,會透過 userinfo endpoint 回傳宣告 (Claims)。這些宣告也可透過 Console > Custom JWT 設定直接包含於 ID 權杖 (ID token) 中。詳情請參閱 自訂 ID 權杖 (Custom ID token)。
custom_data
| Claim name | Type | Description | Included in ID token by default |
|---|---|---|---|
| custom_data | object | 使用者的自訂資料 (The custom data of the user) |
identities
| Claim name | Type | Description | Included in ID token by default |
|---|---|---|---|
| identities | object | 使用者的連結身分 (The linked identities of the user) | |
| sso_identities | array | 使用者的連結 SSO 身分 (The linked SSO identities of the user) |
roles
| Claim name | Type | Description | Included in ID token by default |
|---|---|---|---|
| roles | string[] | 使用者的角色 (The roles of the user) | ✅ |
urn:logto:scope:organizations
| Claim name | Type | Description | Included in ID token by default |
|---|---|---|---|
| organizations | string[] | 使用者所屬的組織 ID (The organization IDs the user belongs to) | ✅ |
| organization_data | object[] | 使用者所屬的組織資料 (The organization data the user belongs to) |
這些組織宣告 (Organization claims) 也可在使用 不透明權杖 (Opaque token) 時,透過 userinfo endpoint 取得。然而,不透明權杖無法作為組織權杖 (Organization tokens) 來存取組織專屬資源。詳見 不透明權杖與組織 (Opaque token and organizations)。
urn:logto:scope:organization_roles
| Claim name | Type | Description | Included in ID token by default |
|---|---|---|---|
| organization_roles | string[] | 使用者所屬組織角色,格式為 <organization_id>:<role_name> (The organization roles the user belongs to with the format of <organization_id>:<role_name>) | ✅ |
API 資源 (API resources) 與組織 (Organizations)
我們建議先閱讀 🔐 角色型存取控制 (RBAC, Role-Based Access Control),以瞭解 Logto RBAC 的基本概念以及如何正確設定 API 資源。
請求 API 資源存取權
若要存取特定 API 資源,請在 device authorization 請求中加入 resource 參數:
curl --request POST 'https://your.logto.endpoint/oidc/device/auth' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=your-application-id' \
--data-urlencode 'scope=openid offline_access' \
--data-urlencode 'resource=https://your-api-resource-indicator'
當使用者完成授權並取得重新整理權杖 (Refresh token) 後,可取得該 API 資源的 JWT 存取權杖 (Access token):
curl --request POST 'https://your.logto.endpoint/oidc/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=your-application-id' \
--data-urlencode 'grant_type=refresh_token' \
--data-urlencode 'refresh_token=REFRESH_TOKEN' \
--data-urlencode 'resource=https://your-api-resource-indicator'
回應將包含 aud 為你的 API 資源標示符 (Resource indicator) 的 JWT access_token。
只有在初始 device authorization 請求包含 offline_access 權限範圍 (Scope) 時才會取得 refresh_token。Logto 採用權杖輪替,請務必儲存並使用最新的 refresh_token。
取得組織權杖 (Organization tokens)
若你對 組織 (Organizations) 不熟悉,請先閱讀 🏢 組織(多租戶,Multi-tenancy)。
若需請求組織相關資訊,請在 device authorization 請求中加入 urn:logto:scope:organizations 權限範圍 (Scope):
curl --request POST 'https://your.logto.endpoint/oidc/device/auth' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=your-application-id' \
--data-urlencode 'scope=openid offline_access urn:logto:scope:organizations' \
--data-urlencode 'resource=urn:logto:resource:organizations'
使用者登入後,可透過重新整理權杖 (Refresh token) 取得組織權杖 (Organization token):
curl --request POST 'https://your.logto.endpoint/oidc/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=your-application-id' \
--data-urlencode 'grant_type=refresh_token' \
--data-urlencode 'refresh_token=REFRESH_TOKEN' \
--data-urlencode 'organization_id=your-organization-id'
回應將包含限定於指定組織的存取權杖 (Access token)。
組織 API 資源 (Organization API resources)
若要取得組織內 API 資源的存取權杖 (Access token),請同時帶入 resource 與 organization_id 參數:
curl --request POST 'https://your.logto.endpoint/oidc/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=your-application-id' \
--data-urlencode 'grant_type=refresh_token' \
--data-urlencode 'refresh_token=REFRESH_TOKEN' \
--data-urlencode 'organization_id=your-organization-id' \
--data-urlencode 'resource=https://your-api-resource-indicator'