跳至主要內容

為你的 Webflow 應用程式新增驗證 (Authentication)

本指南將向你展示如何將 Logto 整合到你的 Webflow 網站中。

提示:

範例專案可在 Webflow 專案預覽 中查看。

先決條件

  1. 將 Logto 與 Webflow 整合需要 Webflow 的「自訂代碼」功能,這至少需要「基本」方案。
  2. 一個 Webflow 網站,可以使用現有網站或創建一個新網站。

整合

初始化 Logto 提供者

備註:

在接下來的步驟中,我們假設你的 Webflow 網站運行在 https://your-awesome-site.webflow.io

在此步驟中,我們將為你的 Webflow 網站新增全域層級的自訂程式碼。由於 Webflow 不支援 NPM,我們將使用 jsdelivr.com CDN 服務來匯入 Logto SDK。

打開「Site settings」頁面,並導航至「Custom code」部分。將以下程式碼新增到「Head code」部分。

<script type="module">
// 從 jsdelivr CDN 匯入 `@logto/browser` SDK
import LogtoClient from 'https://esm.run/@logto/browser';

// 將 `logtoClient` 實例分配給 window 物件,
// 以便在其他頁面中全域使用
window.logtoClient = new LogtoClient({
endpoint: '<your-logto-endpoint>', // 例如 http://localhost:3001
appId: '<your-application-id>',
});
</script>

實作登入

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

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

關於基於重導的登入

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

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


設定登入重定向 URI

讓我們切換到 Logto Console 的應用程式詳細資訊頁面。新增一個重定向 URI https://your-awesome-site.webflow.io/callback,然後點擊「儲存變更」。

Logto Console 中的重定向 URI

實作登入按鈕

返回你的 Webflow 設計器,拖放一個「登入」按鈕到首頁,並為其分配 ID「sign-in」,以便稍後使用 getElementById() 進行引用。

<script type="module">
const signInButton = document.getElementById('sign-in');
const onClickSignIn = () => logtoClient.signIn('https://your-awesome-site.webflow.io/callback');
signInButton.addEventListener('click', onClickSignIn);
</script>

處理重定向

我們快完成了!在最後一步中,我們使用 https://your-awesome-site.webflow.io/callback 作為 Redirect URI,現在需要妥善處理它。

首先,讓我們在 Webflow 中建立一個「Callback」頁面,並簡單地放置一些靜態文字「Redirecting...」。然後在「Callback」頁面添加以下頁面級自訂程式碼。

<script type="module">
(async () => {
// 通過呼叫 SDK 方法處理登入回呼邏輯
await logtoClient.handleSignInCallback(window.location.href);

// 當處理完成後,重新導向回首頁
window.location.assign('https://your-awesome-site.webflow.io');
})();
</script>

實作登出

呼叫 .signOut() 將清除記憶體和 localStorage 中所有的 Logto 資料(如果存在的話)。

登出後,將使用者重新導向回你的网站會是個好主意。我們來將 https://your-awesome-site.webflow.io 添加為管理控制台中的「登出後重定向 URI」(顯示在 Redirect URIs 下)之一,並在呼叫 .signOut() 時使用該 URL 作為參數。

實作登出按鈕

返回 Webflow 設計器,在你的主頁上添加一個「登出」按鈕。同樣地,為按鈕分配一個 ID「sign-out」,並將以下程式碼添加到頁面級自訂程式碼中。

const signOutButton = document.getElementById('sign-out');
const onClickSignOut = () => logtoClient.signOut('https://your-awesome-site.webflow.io');
signOutButton.addEventListener('click', onClickSignOut);

處理驗證 (Authentication) 狀態

在 Logto SDK 中,我們通常可以使用 logtoClient.isAuthenticated() 方法來檢查驗證 (Authentication) 狀態。如果使用者已登入,該值將為 true;否則,將為 false

在你的 Webflow 網站中,你也可以使用此方法以程式化方式顯示和隱藏登入和登出按鈕。應用以下自訂程式碼來相應調整按鈕的 CSS。

const isAuthenticated = await logtoClient.isAuthenticated();

signInButton.style.display = isAuthenticated ? 'none' : 'block';
signOutButton.style.display = isAuthenticated ? 'block' : 'none';

檢查點:測試你的 Webflow 網站

現在,測試你的網站:

  1. 部署並訪問你網站的 URL,應該可以看到登入按鈕。
  2. 點擊登入按鈕,SDK 將啟動登入流程,將你重定向到 Logto 登入頁面。
  3. 登入後,你將被重定向回你的網站,看到使用者名稱和登出按鈕。
  4. 點擊登出按鈕以登出。

獲取使用者資訊

你可以使用這些 Logto 方法以程式化方式檢索使用者資訊:

  • getIdTokenClaims:透過解碼本地 ID 權杖 (ID token) 獲取使用者資訊。某些宣告 (Claims) 可能無法取得。
  • fetchUserInfo:透過向 userinfo endpoint 發送請求來獲取使用者資訊。

需要注意的是,可以檢索的使用者資訊宣告 (Claims) 取決於使用者登入時使用的權限範圍 (Scopes)。考慮到效能和資料大小,ID 權杖 (ID token) 可能不包含所有使用者宣告 (Claims),某些使用者宣告 (Claims) 僅在 userinfo endpoint 中可用(請參閱下方相關列表)。

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

簡而言之,當你請求一個權限範圍 (Scope) 時,你將獲得使用者資訊中的對應宣告 (Claims)。例如,如果你請求 `email` 權限範圍 (Scope),你將獲得使用者的 `email` 和 `email_verified` 資料。

預設情況下,Logto SDK 會始終請求三個權限範圍 (Scopes):`openid`、`profile` 和 `offline_access`,且無法移除這些預設的權限範圍 (Scopes)。但你可以在配置 Logto 時新增更多權限範圍 (Scopes):

<script type="module">
// 從 jsdelivr CDN 匯入 `@logto/browser` SDK
import LogtoClient from 'https://esm.run/@logto/browser';

// 將 `logtoClient` 實例賦值給 window 物件,
// 以便在其他頁面中全域使用
window.logtoClient = new LogtoClient({
endpoint: '<your-logto-endpoint>', // 例如:http://localhost:3001
appId: '<your-application-id>',
scopes: [
UserScope.Email,
UserScope.Phone,
UserScope.CustomData,
UserScope.Identities,
UserScope.Organizations,
],
});
</script>

以下是支援的權限範圍 (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 時新增它們:

<script type="module">
// 從 jsdelivr CDN 匯入 `@logto/browser` SDK
import LogtoClient from 'https://esm.run/@logto/browser';

// 將 `logtoClient` 實例分配給 window 物件,
// 以便在其他頁面中全域使用
window.logtoClient = new LogtoClient({
endpoint: '<your-logto-endpoint>', // 例如:http://localhost:3001
appId: '<your-application-id>',
resources: ['https://shopping.your-app.com/api', 'https://store.your-app.com/api'], // 新增 API 資源
});
</script>

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

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

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

<script type="module">
// 從 jsdelivr CDN 匯入 `@logto/browser` SDK
import LogtoClient from 'https://esm.run/@logto/browser';

window.logtoClient = new LogtoClient({
endpoint: '<your-logto-endpoint>',
appId: '<your-application-id>',
scopes: ['shopping:read', 'shopping:write', 'store:read', 'store:write'],
resources: ['https://shopping.your-app.com/api', 'https://store.your-app.com/api'],
});
</script>

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

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

<script type="module">
// 從 jsdelivr CDN 匯入 `@logto/browser` SDK
import LogtoClient from 'https://esm.run/@logto/browser';

window.logtoClient = new LogtoClient({
endpoint: '<your-logto-endpoint>',
appId: '<your-application-id>',
scopes: ['read', 'write'],
resources: ['https://shopping.your-app.com/api', 'https://store.your-app.com/api'],
});
</script>

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

備註:

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

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

取得 API 資源的存取權杖 (Access token)

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

const isAuthenticated = await logtoClient.isAuthenticated();

if (isAuthenticated) {
(async () => {
const token = await logtoClient.getAccessToken();
console.log(token);
})();
}

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

取得組織權杖 (Organization tokens)

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

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

import LogtoClient, { UserScope } from 'https://esm.run/@logto/browser';

window.logtoClient = new LogtoClient({
// ...other configs
scopes: [UserScope.Organizations],
});

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

import { UserScope } from 'https://esm.run/@logto/browser';

const isAuthenticated = await logtoClient.isAuthenticated();

(async () => {
if (!isAuthenticated) {
return;
}
const claims = await logtoClient.getIdTokenClaims();

console.log('ID 權杖 (ID token) 宣告 (claims):', claims);
console.log('組織 (Organization) IDs:', claims.organizations);

// 假設至少有一個組織 (organization),我們取第一個
const organizationId = claims.organizations[0];
const token = await logtoClient.getOrganizationToken(organizationId);

console.log('組織 (Organization) 存取權杖 (access token):', token);
})();

延伸閱讀

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