Auth.js
Auth.js 是一個廣為人知的身份驗證函式庫,被各種 JS 框架所使用。使用 Auth.js,我們可以減少複雜性。我們還可以訪問一系列身份驗證供應商,例如 GitHub、Google、Facebook 等等。此外,它可以整合到多個框架中,包括 Qwik。
Auth.js 提供了多項功能,可增強簡潔性、生產力、靈活性和供應商多樣性。以下是 Auth.js 的主要功能
- 供應商:Auth.js 支援多個供應商,簡化了我們應用程式中的身份驗證流程(例如,Github、Google、Facebook、Twitter)。它還提供單一登入 (SSO) 服務以及傳統的身份驗證。
- 管理:Auth.js 極大地幫助我們專注於我們的業務邏輯。它可以管理令牌、儲存令牌並自動重新整理令牌。
- 配置:配置 Auth.js 非常簡單。它提供簡單的安裝、錯誤處理、用於登入和註冊的自訂表單,以及與供應商的輕鬆整合。
- 整合:Auth.js 可以與 JS 框架無縫整合,其全面的文件提供了清晰的指南。
- 安全性:雖然 Auth.js 對開發人員很友善,但必須認識到確保我們資料高度安全性的底層複雜性。
請注意,Auth.js 函式庫仍處於 pre-1.0 階段,可能存在錯誤。
安裝
您可以使用以下 Qwik starter 腳本輕鬆新增 Auth.js
npm run qwik add auth
此命令將新增一個新的套件
@auth/qwik
並建立一個名為 [email protected]
的新檔案,其中包含範例配置。
使用 Node 手動部署的注意事項
使用 Node.js 手動部署應用程式時,特別是使用 Express 等框架時,伺服器或 Node 程序本身並不知道它是在 HTTP 還是 HTTPS 下提供服務。與 Vercel、Netlify 或 Cloudflare 等託管服務不同,後者會自動管理 ORIGIN 配置,手動設定需要明確指定。若要確保您的 Node.js 應用程式能夠識別正在提供服務的正確協定和主機,請設定以下環境變數
- ORIGIN:將其設定為您的應用程式 URL。例如:ORIGIN=https://your-app-name.example.com
- PROTOCOL_HEADER:這用於指示客戶端請求的原始協定。通常,這是在代理配置中指定的。將此變數設定為:PROTOCOL_HEADER=X-Forwarded-Proto
- HOST_HEADER:此標頭有助於識別客戶端請求的原始主機。當您的 Node 環境位於代理或負載平衡器之後時,尤其需要此標頭。將此變數設定為:HOST_HEADER=X-Forwarded-Host
Qwik API
useSession
一個 routeLoader$,如果沒有工作階段,則會傳回工作階段物件或空物件。傳回的工作階段物件內容可以使用工作階段回呼進行配置。也可以使用 session REST API 擷取工作階段資料。
import { component$ } from '@builder.io/qwik';
import { useSession } from '~/routes/plugin@auth';
export default component$(() => {
const session = useSession();
return <p>{session.value?.user?.email}</p>;
});
useSignIn
一個 routeAction$,用於啟動登入流程或將使用者傳送到列出所有可用供應商的登入頁面。使用 useSignIn
登入時,CSRF 令牌會在內部處理。
參數
providerId
:一個可選的字串參數,包含供應商的名稱。提供時,將啟動對您的身分識別供應商的授權請求。省略時,將重新導向到內建/無品牌的登入頁面。options
:一個可選的選項物件。redirectTo
:一個可選的字串,指定使用者登入後將被重新導向的網址。預設為發起登入的頁面網址。
authorizationParams
:一個發送到 /authorize 端點的額外參數的可選物件。請參閱授權請求 OIDC 規範以獲取一些想法。
注意: 您也可以透過 provider.authorizationParams 設定設定
authorizationParams
使用 useSignIn
搭配 <Form> 元件以及可選的 providerId
和 options.redirectTo
的範例
import { component$ } from '@builder.io/qwik';
import { Form } from '@builder.io/qwik-city';
import { useSignIn } from '~/routes/plugin@auth';
export default component$(() => {
const signIn = useSignIn();
return (
<Form action={signIn}>
<input type="hidden" name="providerId" value="github" />
<input type="hidden" name="options.redirectTo" value="http://qwik-auth-example.com/dashboard" />
<button>Sign In</button>
</Form>
);
});
以程式設計方式使用 useSignIn
搭配可選的 providerId
和 options.redirectTo
的範例
import { component$ } from '@builder.io/qwik';
import { useSignIn } from '~/routes/plugin@auth';
export default component$(() => {
const signIn = useSignIn();
return (
<button onClick$={() => signIn.submit({ providerId: 'github', options: { redirectTo: 'http://qwik-auth-example.com/dashboard' } })}>Sign In</button>
);
});
useSignOut
一個用於啟動登出流程的 routeAction$。使用者工作階段將失效/從 Cookie/資料庫中移除,具體取決於您選擇用於儲存工作階段的流程。
參數
redirectTo
:一個可選的字串,指定使用者登出後將被重新導向的網址。預設為發起登出的頁面網址。
「redirectTo」必須被重新導向回呼處理程序視為有效。預設情況下,它要求網址是相同主機名稱的絕對網址,或者您也可以提供以斜線開頭的相對網址。如果它不匹配,它將重新導向到首頁。您可以定義自己的重新導向回呼以允許其他網址。
使用 useSignOut
搭配 <Form> 元件以及可選的 redirectTo
的範例
import { component$ } from '@builder.io/qwik';
import { Form } from '@builder.io/qwik-city';
import { useSignOut } from '~/routes/plugin@auth';
export default component$(() => {
const signOut = useSignOut();
return (
<Form action={signOut}>
<input type="hidden" name="redirectTo" value="/signedout" />
<button>Sign Out</button>
</Form>
);
});
以程式設計方式使用 useSignOut
搭配可選的 redirectTo
的範例
import { component$ } from '@builder.io/qwik';
import { useSignOut } from '~/routes/plugin@auth';
export default component$(() => {
const signOut = useSignOut();
return <button onClick$={() => signOut.submit({ redirectTo: '/signedout' })}>Sign Out</button>;
});
REST API
Auth.js 提供的所有相同 REST API 都可用。
登入
GET /api/auth/signin
顯示內建/無品牌的登入頁面。
POST /api/auth/signin/:provider
啟動特定於提供者的登入流程。如果是 OAuth 提供者,呼叫此端點將啟動對您的身分提供者的授權請求。此端點也會被 useSignIn 動作內部使用。
回呼
GET/POST /api/auth/callback/:provider
登出
GET /api/auth/signout
顯示內建/無品牌的登出頁面。
POST /api/auth/signout
處理使用者登出 - 這是一個 POST 提交,用於防止惡意連結在未經使用者同意的情況下觸發使用者登出。使用者工作階段將失效/從 Cookie/資料庫中移除,具體取決於您選擇用於儲存工作階段的流程。此端點也會被 useSignOut 方法內部使用。
工作階段
GET /api/auth/session
返回客戶端安全的工作階段物件 - 如果沒有工作階段,則返回空物件。返回的工作階段物件的內容可以使用工作階段回呼進行設定。工作階段資料也可以使用 useSession 路由器載入器進行檢索。
csrf
GET /api/auth/csrf
返回包含 CSRF 權杖的物件。在 NextAuth.js 中,所有身份驗證路由上都存在 CSRF 保護。它使用「雙重提交 Cookie 方法」,該方法使用已簽名的 HttpOnly、僅限主機的 Cookie。此端點返回的 CSRF 權杖必須作為名為 csrfToken 的表單變數在所有 POST 提交到任何 API 端點時傳遞。
提供者
GET /api/auth/providers
返回已配置的 OAuth 服務清單,以及每個服務的詳細資訊(例如登入和回呼 URL)。這對於動態產生自訂註冊頁面以及檢查為每個已配置的 OAuth 提供者配置了哪些回呼 URL 非常有用。
範例
GitHub
- 按照GitHub OAuth 指南取得您的
GitHub 客戶端 ID
、GitHub 客戶端密鑰
,並使用openssl rand -base64 32
或密鑰產生器產生AUTH_SECRET
。 - 由於預設的
[email protected]
使用 GitHub 作為範例,因此我們不需要在那裡進行任何更改。但是,可以使用 GitHub 以外的提供者,或者可以添加其他提供者。Auth.js 還支援許多可以在此檔案中設定的其他選項。
import { QwikAuth$ } from "@auth/qwik";
import GitHub from "@auth/qwik/providers/github";
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [GitHub],
}),
);
重要提示:請確保保留
onRequest
匯出,因為它用於處理 oAuth 流程重新導向。使用者完成 oAuth 流程後,GitHub(或任何其他提供者)會將使用者重新導向回應用程式的/api/auth/callback/github
(或/api/auth/callback/[其他提供者]
)。onRequest
中介軟體函數將處理對此端點的請求,並完成 oAuth 流程。
- 在專案的根目錄建立或編輯
.env.local
檔案以儲存密鑰
GITHUB_ID=
GITHUB_SECRET=
AUTH_SECRET=
重要提示:請閱讀有關環境變數的 Qwik 文件,以確保您安全地使用它們。許多提供者密鑰應妥善保管,並且不應公開給客戶端/瀏覽器。
- 應用程式現在可以使用 Auth.js 實作身份驗證。
- 好好享受吧!
憑證
警告:Auth.js 不鼓勵使用此功能。
https://next-auth.js.org/providers/credentials
- 基於憑證的身份驗證所提供的功能有意受到限制,以防止使用密碼,因為密碼本身存在安全風險,而且支援使用者名稱和密碼會增加複雜性。
- 由於預設的
[email protected]
使用 GitHub 作為範例,因此我們需要將其替換為憑證。
import { QwikAuth$ } from "@auth/qwik";
import GitHub from "@auth/qwik/providers/github";
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [
Credentials({
async authorize(credentials, req) {
// Add logic here to look up the user from the credentials supplied
const user = {
id: 1,
name: "Mike",
email: "[email protected]",
};
return user;
},
}),
],
}),
);
- 在專案的根目錄建立或編輯
.env.local
檔案以儲存密鑰
AUTH_SECRET=
重要提示:請閱讀有關環境變數的 Qwik 文件,以確保您安全地使用它們。許多提供者密鑰應妥善保管,並且不應公開給客戶端/瀏覽器。
- 應用程式現在可以使用 Auth.js 實作身份驗證。
- 好好享受吧!
路由保護
可以透過路由event.sharedMap
存取會話資料。因此,可以使用位於layout.tsx
或頁面index.tsx
中的類似程式碼來保護路由並進行重新導向
export const onRequest: RequestHandler = (event) => {
const session: Session | null = event.sharedMap.get('session');
if (!session || new Date(session.expires) < new Date()) {
throw event.redirect(302, `/api/auth/signin?redirectTo=${event.url.pathname}`);
}
};
注意:如果放置在 layout.tsx 中,請確保重新導向目的地沒有共用相同的 layout.tsx,否則可能會發生重新導向迴圈。