跳至主要內容

為你的 PHP 網頁應用程式新增驗證 (Authentication)

本指南將向你展示如何將 Logto 整合到你的 PHP 網頁應用程式中。

提示:
  • 範例 使用 Laravel,但對於其他框架,概念是相同的。

先決條件

  • 一個 Logto Cloud 帳戶或自託管的 Logto(如果你還沒有,請查看介紹指南來創建一個)。
  • 一個已創建的 Logto 傳統網頁應用程式。

安裝

composer require logto/sdk

整合

初始化 LogtoClient

首先,建立一個 Logto 配置:

index.php
use Logto\Sdk\LogtoClient;
use Logto\Sdk\LogtoConfig;

$client = new LogtoClient(
new LogtoConfig(
endpoint: "https://you-logto-endpoint.app",
appId: "replace-with-your-app-id",
appSecret: "replace-with-your-app-secret",
),
);
提示:

你可以在管理控制台的應用程式詳細資訊頁面找到並複製「App Secret」:

App Secret

預設情況下,SDK 使用內建的 PHP session 來儲存 Logto 資料。如果你想使用其他儲存方式,可以將自訂的儲存物件作為第二個參數傳入:

index.php
$client = new LogtoClient(
new LogtoConfig(
// ...
),
new YourCustomStorage(),
);

詳情請參閱 Storage

配置重定向 URI

在深入細節之前,以下是終端使用者體驗的快速概覽。登入流程可簡化如下:

  1. 你的應用程式呼叫登入方法。
  2. 使用者被重定向至 Logto 登入頁面。對於原生應用程式,系統瀏覽器會被開啟。
  3. 使用者登入後被重定向回你的應用程式(配置為重定向 URI)。

關於基於重導的登入

  1. 此驗證流程遵循 OpenID Connect (OIDC) 協議,Logto 強制執行嚴格的安全措施以保護使用者登入。
  2. 如果你有多個應用程式,可以使用相同的身分提供者 (IdP, Identity provider)(Logto)。一旦使用者登入其中一個應用程式,Logto 將在使用者訪問另一個應用程式時自動完成登入流程。

欲了解更多關於基於重導登入的原理和優勢,請參閱 Logto 登入體驗解析


備註:

在以下的程式碼片段中,我們假設你的應用程式運行在 http://localhost:3000/

配置重定向 URI

切換到 Logto Console 的應用程式詳細資訊頁面。新增一個重定向 URI http://localhost:3000/callback

Logto Console 中的重定向 URI

就像登入一樣,使用者應被重定向到 Logto 以登出共享會話。完成後,將使用者重定向回你的網站會很不錯。例如,將 http://localhost:3000/ 新增為登出後重定向 URI 區段。

然後點擊「儲存」以保存更改。

處理回調

使用者登入後,Logto 會將使用者重定向到你在 Logto Console 中設定的回呼 URL。在此範例中,我們使用 /callback 作為回呼 URL:

Route::get('/callback', function () {
try {
$client->handleSignInCallback(); // 處理許多事情
} catch (\Throwable $exception) {
return $exception; // 將此更改為你的錯誤處理邏輯
}
return redirect('/'); // 成功登入後將使用者重定向到首頁
});

實作登入路由

在你的網頁應用程式中,新增一個路由來正確處理使用者的登入請求。例如:

Route::get('/sign-in', function () {
return redirect($client->signIn('http://localhost:3000/callback'));
});

http://localhost:3000/callback 替換為你在 Logto Console 中為此應用程式設定的回呼 URL。

如果你希望在第一個畫面顯示註冊頁面,可以將 interactionMode 設為 signUp

Route::get('/sign-in', function () {
return redirect($client->signIn('http://localhost:3000/callback', InteractionMode::signUp));
});

現在,無論何時你的使用者造訪 http://localhost:3000/sign-in,都會開始新的登入嘗試並將使用者重定向到 Logto 登入頁面。

注意 建立登入路由並不是開始登入嘗試的唯一方法。你可以隨時使用 signIn 方法來獲取登入 URL 並將使用者重定向到該 URL。

實作登出路由

當使用者發出登出請求後,Logto 將會清除會話中的所有使用者驗證資訊。

要清理 PHP 會話和 Logto 會話,可以實作一個登出路由,如下所示:

Route::get('/sign-out', function () {
return redirect(
// 成功登出後將使用者重定向到首頁
$client->signOut('http://localhost:3000/')
);
});

postLogoutRedirectUri 是可選的,如果未提供,使用者在成功登出後將被重定向到 Logto 的預設頁面(不會重定向回你的應用程式)。

注意 名稱 postLogoutRedirectUri 來自 OpenID Connect RP-Initiated Logout 規範。雖然 Logto 使用「登出」而非「logout」,但概念相同。

處理驗證狀態

在 Logto SDK 中,我們可以使用 $client->isAuthenticated() 來檢查驗證 (Authentication) 狀態。如果使用者已登入,該值將為 true,否則為 false。

我們還需要實作一個首頁來進行示範:

  • 如果使用者未登入,顯示一個登入按鈕;
  • 如果使用者已登入,顯示一個登出按鈕。
Route::get('/', function () {
if ($client->isAuthenticated() === false) {
return "未驗證 <a href='/sign-in'>登入</a>";
}

return "<a href='/sign-out'>登出</a>";
});

檢查點:測試你的應用程式

現在,你可以測試你的應用程式:

  1. 執行你的應用程式,你會看到登入按鈕。
  2. 點擊登入按鈕,SDK 會初始化登入流程並將你重定向到 Logto 登入頁面。
  3. 登入後,你將被重定向回應用程式並看到登出按鈕。
  4. 點擊登出按鈕以清除權杖存儲並登出。

獲取使用者資訊

顯示使用者資訊

要顯示使用者資訊,你可以使用 getIdTokenClaims 方法或 fetchUserInfo 方法來獲取使用者資訊。getIdTokenClaims 返回 ID 權杖 (ID token) 中包含的使用者資訊,而 fetchUserInfo 則從 userinfo 端點獲取使用者資訊。

index.php
Route::get('/userinfo', function () {
if ($client->isAuthenticated() === false) {
return "未驗證 <a href='/sign-in'>登入</a>";
}

return (
// 獲取本地 ID 權杖宣告 (Claims)
json_decode($client->getIdTokenClaims())
. "<br>"
// 從 Logto userinfo 端點獲取使用者資訊
json_decode($client->fetchUserInfo())
);
});

我們的資料模型基於 JsonModel,在編碼或解碼 JSON 時可以安全地接受未定義的鍵。

請注意,值為 null 的欄位(宣告 (Claim))並不意味著該欄位已設置。原因可能是相關的權限範圍 (Scope) 未被請求,或使用者沒有該欄位。

例如,如果我們在登入時未請求 email 權限範圍 (Scope),則 email 欄位將為 null。然而,如果我們請求了 email 權限範圍 (Scope),則 email 欄位將是使用者的電子郵件地址(如果有的話)。

請求額外的宣告 (Claims)

你可能會發現從 getIdTokenClaims 返回的物件中缺少一些使用者資訊。這是因為 OAuth 2.0 和 OpenID Connect (OIDC) 的設計遵循最小權限原則 (PoLP, Principle of Least Privilege),而 Logto 是基於這些標準構建的。

預設情況下,僅返回有限的宣告 (Claims)。如果你需要更多資訊,可以請求額外的權限範圍 (Scopes) 以存取更多宣告。

資訊:

「宣告 (Claim)」是對主體所做的斷言;「權限範圍 (Scope)」是一組宣告。在目前的情況下,宣告是關於使用者的一部分資訊。

以下是權限範圍與宣告關係的非規範性範例:

提示:

「sub」宣告表示「主體 (Subject)」,即使用者的唯一識別符(例如使用者 ID)。

Logto SDK 將始終請求三個權限範圍:openidprofileoffline_access

要請求額外的權限範圍 (Scopes),你可以在初始化 Logto 客戶端時配置 scopes 選項:

index.php
$client = new LogtoClient(
new LogtoConfig(
// ...其他配置
scopes: ["email", "phone"], // 根據需要更新
),
);

或者,你可以使用 UserScope 列舉來新增權限範圍 (Scopes):

index.php
use Logto\Sdk\Constants\UserScope;

$client = new LogtoClient(
new LogtoConfig(
// ...其他配置
scopes: [UserScope::email, UserScope::phone], // 根據需要更新
),
);

然後,額外的宣告 (Claims) 將在 getIdTokenClaimsfetchUserInfo 的返回值中可用。

需要網路請求的宣告 (Claims)

為了防止 ID 權杖 (ID token) 膨脹,某些宣告 (Claims) 需要透過網路請求來獲取。例如,即使在權限範圍 (Scopes) 中請求了 custom_data 宣告,它也不會包含在使用者物件中。要存取這些宣告,你可以使用 fetchUserInfo 方法

index.php
$client->fetchUserInfo()->custom_data;
此方法將透過請求 userinfo 端點來獲取使用者資訊。要了解更多可用的權限範圍 (Scopes) 和宣告 (Claims),請參閱 權限範圍 (Scopes) 和宣告 (Claims) 部分。

權限範圍 (Scopes) 和宣告 (Claims)

Logto 使用 OIDC 權限範圍 (Scopes) 和宣告 (Claims) 慣例 來定義從 ID 權杖 (ID token) 和 OIDC 使用者資訊端點 (userinfo endpoint) 獲取使用者資訊的權限範圍和宣告。無論是「權限範圍 (Scope)」還是「宣告 (Claim)」,都是 OAuth 2.0 和 OpenID Connect (OIDC) 規範中的術語。

以下是支援的權限範圍 (Scopes) 及其對應的宣告 (Claims):

openid

宣告名稱類型描述需要使用者資訊嗎?
substring使用者的唯一識別符

profile

宣告名稱類型描述需要使用者資訊嗎?
namestring使用者的全名
usernamestring使用者的用戶名
picturestring使用者個人資料圖片的 URL。此 URL 必須指向圖像文件(例如 PNG、JPEG 或 GIF 圖像文件),而不是包含圖像的網頁。請注意,此 URL 應特別參考適合在描述使用者時顯示的個人資料照片,而不是使用者拍攝的任意照片。
created_atnumber使用者創建的時間。時間以自 Unix epoch(1970-01-01T00:00:00Z)以來的毫秒數表示。
updated_atnumber使用者資訊最後更新的時間。時間以自 Unix epoch(1970-01-01T00:00:00Z)以來的毫秒數表示。

其他 標準宣告 包括 family_namegiven_namemiddle_namenicknamepreferred_usernameprofilewebsitegenderbirthdatezoneinfolocale 也將包含在 profile 權限範圍中,無需請求使用者資訊端點。與上述宣告的不同之處在於,這些宣告僅在其值不為空時返回,而上述宣告在值為空時將返回 null

備註:

與標準宣告不同,created_atupdated_at 宣告使用毫秒而非秒。

email

宣告名稱類型描述需要使用者資訊嗎?
emailstring使用者的電子郵件地址
email_verifiedboolean電子郵件地址是否已驗證

phone

宣告名稱類型描述需要使用者資訊嗎?
phone_numberstring使用者的電話號碼
phone_number_verifiedboolean電話號碼是否已驗證

address

請參閱 OpenID Connect Core 1.0 以獲取地址宣告的詳細資訊。

custom_data

宣告名稱類型描述需要使用者資訊嗎?
custom_dataobject使用者的自訂資料

identities

宣告名稱類型描述需要使用者資訊嗎?
identitiesobject使用者的連結身分
sso_identitiesarray使用者的連結 SSO 身分

roles

宣告名稱類型描述需要使用者資訊嗎?
rolesstring[]使用者的角色

urn:logto:scope:organizations

宣告名稱類型描述需要使用者資訊嗎?
organizationsstring[]使用者所屬的組織 ID
organization_dataobject[]使用者所屬的組織資料

urn:logto:scope:organization_roles

宣告名稱類型描述需要使用者資訊嗎?
organization_rolesstring[]使用者所屬的組織角色,格式為 <organization_id>:<role_name>

考慮到效能和資料大小,如果「需要使用者資訊嗎?」為「是」,則表示該宣告不會顯示在 ID 權杖中,而會在 使用者資訊端點 回應中返回。

API 資源與組織

我們建議先閱讀 🔐 角色型存取控制 (RBAC, Role-Based Access Control),以瞭解 Logto RBAC 的基本概念以及如何正確設定 API 資源。

配置 Logto 客戶端

一旦你設定了 API 資源,就可以在應用程式中配置 Logto 時新增它們:

index.php
$client = new LogtoClient(
new LogtoConfig(
// ...other configs
resources: ["https://shopping.your-app.com/api", "https://store.your-app.com/api"], // 新增 API 資源 (API resources)
),
);

每個 API 資源都有其自身的權限(權限範圍)。

例如,https://shopping.your-app.com/api 資源具有 shopping:readshopping:write 權限,而 https://store.your-app.com/api 資源具有 store:readstore:write 權限。

要請求這些權限,你可以在應用程式中配置 Logto 時新增它們:

index.php
$client = new LogtoClient(
new LogtoConfig(
// ...other configs
scopes: ["shopping:read", "shopping:write", "store:read", "store:write"], // 新增權限範圍 (Scopes)
resources: ["https://shopping.your-app.com/api", "https://store.your-app.com/api"], // 新增 API 資源 (API resources)
),
);

你可能會注意到權限範圍是獨立於 API 資源定義的。這是因為 OAuth 2.0 的資源標示符 (Resource Indicators) 指定請求的最終權限範圍將是所有目標服務中所有權限範圍的笛卡兒積。

因此,在上述情況中,權限範圍可以從 Logto 的定義中簡化,兩個 API 資源都可以擁有 read write 權限範圍而不需要前綴。然後,在 Logto 配置中:

index.php
$client = new LogtoClient(
new LogtoConfig(
// ...other configs
scopes: ["read", "write"], // 新增權限範圍 (Scopes)
resources: ["https://shopping.your-app.com/api", "https://store.your-app.com/api"], // 新增 API 資源 (API resources)
),
);

對於每個 API 資源,它將請求 readwrite 權限範圍。

備註:

請求未在 API 資源中定義的權限範圍是可以的。例如,即使 API 資源中沒有可用的 email 權限範圍,你也可以請求 email 權限範圍。不可用的權限範圍將被安全地忽略。

成功登入後,Logto 將根據使用者的角色向 API 資源發出適當的權限範圍。

獲取 API 資源的存取權杖

要獲取特定 API 資源的存取權杖 (Access token),你可以使用 GetAccessToken 方法:

index.php
$accessToken = $client->getAccessToken("https://shopping.your-app.com/api");

此方法將返回一個 JWT 存取權杖 (Access token),當使用者擁有相關權限時,可以用來存取 API 資源。如果當前快取的存取權杖 (Access token) 已過期,此方法將自動嘗試使用重新整理權杖 (Refresh token) 獲取新的存取權杖 (Access token)。

獲取組織權杖

如果你對組織 (Organization) 不熟悉,請閱讀 🏢 組織(多租戶,Multi-tenancy) 以開始了解。

在配置 Logto client 時,你需要新增 core.UserScopeOrganizations 權限範圍 (scope):

use Logto\Sdk\Constants\UserScope;

$client = new LogtoClient(
new LogtoConfig(
// ...other configs
scopes: [UserScope::organizations], // 新增權限範圍 (Scopes)
),
);

使用者登入後,你可以為使用者獲取組織權杖 (organization token):

index.php
# 將參數替換為有效的組織 (Organization) ID。
# 使用者的有效組織 (Organization) ID 可在 ID 權杖 (ID token) 宣告 (claim) `organizations` 中找到。
$organizationToken = $client->getOrganizationToken(organization_id);
# 或
$claims = $client->getOrganizationTokenClaims(organization_id);

延伸閱讀

終端使用者流程:驗證流程、帳戶流程與組織流程 配置連接器 保護你的 API