为你的 Webflow 应用添加认证 (Authentication)
本指南将向你展示如何将 Logto 集成到你的 Webflow 网站中。
- 示例项目可在 Webflow 项目预览 查看。
- 在线演示部署在 https://charless-trendy-site-f191c0.webflow.io/。
前提条件
- 将 Logto 与 Webflow 集成需要 Webflow 的“自定义代码”功能,这至少需要“基础”计划。
- 一个 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>
实现登录
在我们深入细节之前,这里是终端用户体验的快速概述。登录过程可以简化如下:
- 你的应用调用登录方法。
- 用户被重定向到 Logto 登录页面。对于原生应用,将打开系统浏览器。
- 用户登录并被重定向回你的应用(配置为重定向 URI)。
关于基于重定向的登录
- 此认证 (Authentication) 过程遵循 OpenID Connect (OIDC) 协议,Logto 强制执行严格的安全措施以保护用户登录。
- 如果你有多个应用程序,可以使用相同的身份提供商 (IdP)(日志 (Logto))。一旦用户登录到一个应用程序,当用户访问另一个应用程序时,Logto 将自动完成登录过程。
要了解有关基于重定向的登录的原理和好处的更多信息,请参阅 Logto 登录体验解释。
配置登录重定向 URI
让我们切换到 Logto Console 的应用详情页面。添加一个重定向 URI https://your-awesome-site.webflow.io/callback
并点击“保存更改”。
实现登录按钮
返回到你的 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
作为重定向 URI,现在我们需要正确处理它。
首先,让我们在 Webflow 中创建一个“Callback”页面,并简单地放置一些静态文本“正在重定向...”。然后将以下页面级自定义代码添加到“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(显示在重定向 URI 下),并在调用 .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 网站
现在,测试你的网站:
- 部署并访问你的网站 URL,登录按钮应该是可见的。
- 点击登录按钮,SDK 将启动登录过程,将你重定向到 Logto 登录页面。
- 登录后,你将被重定向回你的网站,看到用户名和退出按钮。
- 点击退出按钮以退出。
获取用户信息
你可以使用这些 Logto 方法以编程方式检索用户信息:
获取 ID 令牌 (ID token) 声明 (Claims)
:通过解码本地 ID 令牌获取用户信息。某些声明可能不可用。获取用户信息
:通过向 userinfo endpoint 发送请求获取用户信息。
需要注意的是,可以检索的用户信息声明取决于用户在登录时使用的权限,并且考虑到性能和数据大小,ID 令牌可能不包含所有用户声明,某些用户声明仅在 userinfo endpoint 中可用(请参阅下面的相关列表)。
Logto 使用 OIDC 权限和声明约定 来定义从 ID 令牌和 OIDC 用户信息端点检索用户信息的权限和声明。“权限”和“声明”都是 OAuth 2.0 和 OpenID Connect (OIDC) 规范中的术语。
简而言之,当你请求一个权限时,你将获得用户信息中的相应声明。例如,如果你请求 `email` 权限,你将获得用户的 `email` 和 `email_verified` 数据。
默认情况下,Logto SDK 总是会请求三个权限:`openid`、`profile` 和 `offline_access`,并且无法移除这些默认权限。但你可以在配置 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>',
scopes: [
UserScope.Email,
UserScope.Phone,
UserScope.CustomData,
UserScope.Identities,
UserScope.Organizations,
],
});
</script>
以下是支持的权限(Scopes)及其对应的声明(Claims)列表:
openid
声明名称 | 类型 | 描述 | 需要用户信息吗? |
---|---|---|---|
sub | string | 用户的唯一标识符 | 否 |
profile
声明名称 | 类型 | 描述 | 需要用户信息吗? |
---|---|---|---|
name | string | 用户的全名 | 否 |
username | string | 用户名 | 否 |
picture | string | 终端用户的个人资料图片的 URL。此 URL 必须指向一个图像文件(例如,PNG、JPEG 或 GIF 图像文件),而不是包含图像的网页。请注意,此 URL 应特别引用适合在描述终端用户时显示的终端用户的个人资料照片,而不是终端用户拍摄的任意照片。 | 否 |
created_at | number | 终端用户创建的时间。时间表示为自 Unix 纪元(1970-01-01T00:00:00Z)以来的毫秒数。 | 否 |
updated_at | number | 终端用户信息最后更新的时间。时间表示为自 Unix 纪元(1970-01-01T00:00:00Z)以来的毫秒数。 | 否 |
其他 标准声明 包括 family_name
、given_name
、middle_name
、nickname
、preferred_username
、profile
、website
、gender
、birthdate
、zoneinfo
和 locale
也将包含在 profile
权限中,而无需请求用户信息端点。与上述声明的区别在于,这些声明只有在其值不为空时才会返回,而上述声明在值为空时将返回 null
。
与标准声明不同,created_at
和 updated_at
声明使用毫秒而不是秒。
email
声明名称 | 类型 | 描述 | 需要用户信息吗? |
---|---|---|---|
string | 用户的电子邮件地址 | 否 | |
email_verified | boolean | 电子邮件地址是否已验证 | 否 |
phone
声明名称 | 类型 | 描述 | 需要用户信息吗? |
---|---|---|---|
phone_number | string | 用户的电话号码 | 否 |
phone_number_verified | boolean | 电话号码是否已验证 | 否 |
address
请参阅 OpenID Connect Core 1.0 以获取地址声明的详细信息。
custom_data
声明名称 | 类型 | 描述 | 需要用户信息吗? |
---|---|---|---|
custom_data | object | 用户的自定义数据 | 是 |
identities
声明名称 | 类型 | 描述 | 需要用户信息吗? |
---|---|---|---|
identities | object | 用户的关联身份 | 是 |
sso_identities | array | 用户的关联 SSO 身份 | 是 |
urn:logto:scope:organizations
声明名称 | 类型 | 描述 | 需要用户信息吗? |
---|---|---|---|
organizations | string[] | 用户所属的组织 ID | 否 |
organization_data | object[] | 用户所属的组织数据 | 是 |
urn:logto:scope:organization_roles
声明名称 | 类型 | 描述 | 需要用户信息吗? |
---|---|---|---|
organization_roles | string[] | 用户所属的组织角色,格式为 <organization_id>:<role_name> | 否 |
考虑到性能和数据大小,如果“需要用户信息吗?”为“是”,则表示声明不会显示在 ID 令牌中,而会在 用户信息端点 响应中返回。
API 资源
我们建议首先阅读 🔐 基于角色的访问控制 (RBAC),以了解 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 资源 (API resources)
});
</script>
每个 API 资源都有其自己的权限(权限)。
例如,https://shopping.your-app.com/api
资源具有 shopping:read
和 shopping:write
权限,而 https://store.your-app.com/api
资源具有 store:read
和 store: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 的资源指示器 指定请求的最终权限将是所有目标服务中所有权限的笛卡尔积。
因此,在上述情况下,权限可以从 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 资源,它将请求 read
和 write
权限。
请求 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)
如果你对组织不熟悉,请阅读 🏢 组织(多租户) 以开始了解。
在配置 Logto 客户端时,你需要添加 UserScope.Organizations
权限:
import LogtoClient, { UserScope } from 'https://esm.run/@logto/browser';
window.logtoClient = new LogtoClient({
// ...other configs
scopes: [UserScope.Organizations],
});
用户登录后,你可以获取用户的组织令牌:
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);
})();