跳至主要內容

為你的 Passport.js 應用程式新增驗證 (Authentication)

本指南將向你展示如何使用 Passport.js 和 OIDC 策略將 Logto 整合到你的應用程式中。

提示:
  • 在本指南中,我們假設你已在專案中設置了帶有 session 的 Express。如果還沒有,請查看 Express.js 官網 以開始使用。

先決條件

安裝

透過你喜愛的套件管理器安裝 Logto SDK:

npm i passport passport-openidconnect

整合

使用 OIDC 策略初始化 Passport.js

passport.ts
import passport from 'passport';
import OpenIDConnectStrategy, { type Profile, type VerifyCallback } from 'passport-openidconnect';

const endpoint = '<your-logto-endpoint>';
const appId = '<your-application-id>';
const appSecret = '<your-application-secret>';

export default function initPassport() {
passport.use(
new OpenIDConnectStrategy(
{
issuer: `${endpoint}/oidc`,
authorizationURL: `${endpoint}/oidc/auth`,
tokenURL: `${endpoint}/oidc/token`,
userInfoURL: `${endpoint}/oidc/me`,
clientID: appId,
clientSecret: appSecret,
callbackURL: '/callback',
scope: ['profile', 'offline_access'],
},
(issuer: string, profile: Profile, callback: VerifyCallback) => {
callback(null, profile);
}
)
);

passport.serializeUser((user, callback) => {
callback(null, user);
});

passport.deserializeUser(function (user, callback) {
callback(null, user as Express.User);
});
}

此程式碼使用 OpenIDConnectStrategy 初始化 Passport。序列化和反序列化方法設置僅供示範。

確保在應用程式中初始化並附加 Passport 中介軟體:

your-app-entry.ts
import initPassport from './passport';

// ... other code
initPassport();
// ... other code
app.use(passport.authenticate('session'));
// ... other code

配置重定向 URI

在進入細節之前,這裡先快速說明一下終端使用者的體驗。登入流程可簡化如下:

  1. 你的應用程式呼叫登入方法。
  2. 使用者被重新導向至 Logto 登入頁面。對於原生應用程式,會開啟系統瀏覽器。
  3. 使用者登入後,會被重新導向回你的應用程式(設定為 redirect 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 區段。

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

實作登入與登出

我們現在將為驗證流程創建特定路由:

your-app-entry.ts
app.get('/sign-in', passport.authenticate('openidconnect'));
app.get(
'/callback',
passport.authenticate('openidconnect', {
successReturnToOrRedirect: '/',
})
);
app.get('/sign-out', (request, response, next) => {
request.logout((error) => {
if (error) {
next(error);
return;
}
response.redirect(`${endpoint}/oidc/session/end?client_id=${appId}`);
});
});

然後新增到首頁

your-app-entry.ts
app.get('/', (request: Request, response) => {
const { user } = request;
response.setHeader('content-type', 'text/html');

if (user) {
response.end(
`<h1>Hello Logto</h1><p>Signed in as ${JSON.stringify(
user
)}, <a href="/sign-out">Sign Out</a></p>`
);
} else {
response.end(`<h1>Hello Logto</h1><p><a href="/sign-in">Sign In</a></p>`);
}
});

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

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

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

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

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):

export default function initPassport() {
passport.use(
new OpenIDConnectStrategy(
{
// ... other options
clientID: appId,
clientSecret: appSecret,
callbackURL: '/callback',
scope: ['openid', 'offline_access', 'profile', 'email'],
}
// ... other options
)
);
// ... other options
}

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

openid

Claim nameTypeDescriptionNeeds userinfo?
substring使用者的唯一識別符No

profile

Claim nameTypeDescriptionNeeds userinfo?
namestring使用者的全名No
usernamestring使用者的使用者名稱No
picturestring終端使用者大頭貼的 URL。此 URL 必須指向圖片檔案(例如 PNG、JPEG 或 GIF),而非包含圖片的網頁。請注意,此 URL 應明確指向適合描述終端使用者的個人照片,而非終端使用者隨意拍攝的照片。No
created_atnumber終端使用者建立的時間。時間以自 Unix 紀元(1970-01-01T00:00:00Z)以來的毫秒數表示。No
updated_atnumber終端使用者資訊最後更新的時間。時間以自 Unix 紀元(1970-01-01T00:00:00Z)以來的毫秒數表示。No

其他 標準宣告 (Standard claims) 包含 family_namegiven_namemiddle_namenicknamepreferred_usernameprofilewebsitegenderbirthdatezoneinfolocale 也會包含在 profile 權限範圍內,無需額外請求 userinfo endpoint。與上表宣告不同的是,這些宣告僅在其值不為空時才會返回,而上表宣告若值為空則會返回 null

備註:

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

email

Claim nameTypeDescriptionNeeds userinfo?
emailstring使用者的電子郵件地址No
email_verifiedboolean電子郵件地址是否已驗證No

phone

Claim nameTypeDescriptionNeeds userinfo?
phone_numberstring使用者的手機號碼No
phone_number_verifiedboolean手機號碼是否已驗證No

address

請參閱 OpenID Connect Core 1.0 以瞭解 address 宣告的詳細資訊。

custom_data

Claim nameTypeDescriptionNeeds userinfo?
custom_dataobject使用者的自訂資料Yes

identities

Claim nameTypeDescriptionNeeds userinfo?
identitiesobject使用者的連結身分Yes
sso_identitiesarray使用者的連結 SSO 身分Yes

roles

Claim nameTypeDescriptionNeeds userinfo?
rolesstring[]使用者的角色 (Roles)No

urn:logto:scope:organizations

Claim nameTypeDescriptionNeeds userinfo?
organizationsstring[]使用者所屬的組織 (Organizations) IDNo
organization_dataobject[]使用者所屬的組織 (Organizations) 資料Yes
備註:

這些組織 (Organizations) 宣告也可透過 userinfo endpoint 取得(當使用 不透明權杖 (Opaque token) 時)。但不透明權杖 (Opaque tokens) 無法作為組織權杖 (Organization tokens) 存取組織專屬資源。詳情請見 不透明權杖 (Opaque token) 與組織 (Organizations)

urn:logto:scope:organization_roles

Claim nameTypeDescriptionNeeds userinfo?
organization_rolesstring[]使用者所屬的組織 (Organizations) 角色 (Roles),格式為 <organization_id>:<role_name>No

考量效能與資料大小,若 "Needs userinfo?" 為 "Yes",表示該宣告 (Claim) 不會出現在 ID 權杖 (ID token) 中,而會在 userinfo endpoint 回應中返回。

延伸閱讀

終端使用者流程:驗證流程、帳號流程與組織流程 (End-user flows: authentication flows, account flows, and organization flows) 設定連接器 (Configure connectors) 授權 (Authorization)