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 库仍处于 1.0 版本之前的阶段,可能存在错误。

安装

您可以使用以下 Qwik 起始脚本轻松添加 Auth.js:

npm run qwik add auth

此命令将添加以下新包:

  • @auth/core
  • @builder.io/qwik-auth

并创建一个名为 plugin@auth.ts 的新文件,其中包含一个示例配置。

需要手动操作

在使用 npm run qwik add auth 安装 auth 包之后,需要将 @auth/core 包添加到 vite.config.js 文件的依赖优化设置中:

vite.config.js
export default defineConfig(() => {
  return {
    plugins: [qwikCity(), qwikVite(), tsconfigPaths()],
    dev: {
      headers: {
        'Cache-Control': 'public, max-age=0',
      },
    },
    preview: {
      headers: {
        'Cache-Control': 'public, max-age=600',
      },
    },
    optimizeDeps: {
      include: [ "@auth/core" ]
    }
  };
});

Qwik API

useAuthSession

一个 routeLoader$,如果存在会返回一个会话对象,否则返回一个空对象。返回的会话对象的内容可以通过会话回调进行配置。还可以使用 session REST API 来检索会话数据。

import { component$ } from '@builder.io/qwik';
import { useAuthSession } from '~/routes/plugin@auth';
 
export default component$(() => {
  const session = useAuthSession();
  return <p>{session.value?.user?.email}</p>;
});
 

useAuthSignin

一个 routeAction$,用于启动登录流程或将用户发送到登录页面,列出所有可能的提供者。在使用 useAuthSignin 进行登录时,CSRF 令牌会在内部处理。

参数

  • providerId:可选的字符串参数,提供者的名称。如果提供,将启动到身份提供者的授权请求。如果省略,将重定向到内置/无品牌的登录页面。
  • options:可选的选项对象。
    • callbackUrl:可选的字符串,指定用户在登录后将被重定向到的 URL。默认为发起登录的页面 URL。
  • authorizationParams:可选的附加参数对象,发送到 /authorize 端点。有关一些想法,请参阅授权请求 OIDC 规范。

注意:您还可以通过 provider.authorizationParams 配置设置 authorizationParams

使用 <Form> 组件和可选的 providerIdoptions.callbackUrl 来示例使用 useAuthSignin

import { component$ } from '@builder.io/qwik';
import { Form } from '@builder.io/qwik-city';
import { useAuthSignin } from '~/routes/plugin@auth';
 
export default component$(() => {
  const signIn = useAuthSignin();
  return (
    <Form action={signIn}>
      <input type="hidden" name="providerId" value="github" />
      <input type="hidden" name="options.callbackUrl" value="http://qwik-auth-example.com/dashboard" />
      <button>登录</button>
    </Form>
  );
});

使用可选的 providerIdoptions.callbackUrl 来示例使用 useAuthSignin

import { component$ } from '@builder.io/qwik';
import { useAuthSignin } from '~/routes/plugin@auth';
 
export default component$(() => {
  const signIn = useAuthSignin();
  return (
    <button onClick$={() => signIn.submit({ providerId: 'github', options: { callbackUrl: 'http://qwik-auth-example.com/dashboard' } })}>登录</button>
  );
});

useAuthSignout

一个 routeAction$,用于启动登出流程。用户会话将被作废/从 cookie/数据库中删除,具体取决于您选择存储会话的流程。

参数

  • callbackUrl:可选的字符串,指定用户在登出后将被重定向到的 URL。默认为发起登录的页面 URL。

'callbackUrl' 必须被重定向回调处理程序视为有效。默认情况下,它要求 URL 是同一主机名下的绝对 URL,或者您还可以提供以斜杠开头的相对 URL。如果不匹配,将重定向到主页。您可以定义自己的重定向回调以允许其他 URL。

使用 <Form> 组件和可选的 callbackUrl 来示例使用 useAuthSignout

import { component$ } from '@builder.io/qwik';
import { Form } from '@builder.io/qwik-city';
import { useAuthSignout } from '~/routes/plugin@auth';
 
export default component$(() => {
  const signOut = useAuthSignout();
  return (
    <Form action={signOut}>
      <input type="hidden" name="callbackUrl" value="/signedout" />
      <button>登出</button>
    </Form>
  );
});

使用可选的 callbackUrl 来示例使用 useAuthSignout

import { component$ } from '@builder.io/qwik';
import { useAuthSignout } from '~/routes/plugin@auth';
 
export default component$(() => {
  const signOut = useAuthSignout();
  return <button onClick$={() => signOut.submit({ callbackUrl: '/signedout' })}>登出</button>;
});

REST API

提供了与 Auth.js 提供的相同的 REST API。

signin

GET /api/auth/signin

显示内置/无品牌的登录页面。

POST /api/auth/signin/:provider

启动特定于提供者的登录流程。对于 OAuth 提供者,调用此端点将启动到您的身份提供者的授权请求。此端点也由 useAuthSignin 方法在内部使用。

callback

GET/POST /api/auth/callback/:provider

signout

GET /api/auth/signout

显示内置/无品牌的登出页面。

POST /api/auth/signout

处理用户登出 - 这是一个 POST 提交,以防止恶意链接触发未经用户同意的用户登出。用户会话将被作废/从 cookie/数据库中删除,具体取决于您选择存储会话的流程。此端点也由 useAuthSignout 方法在内部使用。

session

GET /api/auth/session

返回客户端安全的会话对象 - 如果没有会话,则返回一个空对象。返回的会话对象的内容可以通过会话回调进行配置。还可以使用 useAuthSession routerLoader 来检索会话数据。

csrf

GET /api/auth/csrf

返回包含 CSRF 令牌的对象。在 NextAuth.js 中,所有身份验证路由都具有 CSRF 保护。它使用“双重提交 cookie 方法”,该方法使用了一个带有签名的 HttpOnly、仅限主机的 cookie。此端点返回的 CSRF 令牌必须作为名为 csrfToken 的表单变量传递给任何 API 端点的所有 POST 提交。

providers

GET /api/auth/providers

返回配置的 OAuth 服务和每个服务的详细信息(例如登录和回调 URL)的列表。它对于动态生成自定义注册页面以及检查为每个配置的 OAuth 提供者配置了哪些回调 URL 非常有用。

示例

GitHub

  1. 按照 GitHub OAuth 指南 获取您的 GitHub 客户端 IDGitHub 客户端密钥,并使用 openssl rand -base64 32Secret Generator 生成 AUTH_SECRET
  2. 由于默认的 plugin@auth.ts 使用 GitHub 作为示例,我们不需要在那里进行任何更改。但是可以使用除 GitHub 之外的其他提供者,或者添加其他提供者。Auth.js 还支持许多其他选项,可以在此文件中设置。
src/routes/plugin@auth.ts
import { serverAuth$ } from '@builder.io/qwik-auth';
import GitHub from '@auth/core/providers/github';
import type { Provider } from '@auth/core/providers';
 
export const { onRequest, useAuthSession, useAuthSignin, useAuthSignout } = serverAuth$(
  ({ env }) => ({
    secret: env.get("AUTH_SECRET"),
    trustHost: true,
    providers: [
      GitHub({
        clientId: env.get("GITHUB_ID"),
        clientSecret: env.get("GITHUB_SECRET"),
      }),
    ] as Provider[],
  })
);

重要提示:请确保保持 onRequest 导出,因为它用于处理 oAuth 流重定向。用户完成 oAuth 流后,GitHub(或任何其他提供者)将重定向用户返回到应用程序的 /api/auth/callback/github(或 /api/auth/callback/[otherProvider])。onRequest 中间件函数将处理对此端点的请求并完成 oAuth 流。

  1. 创建或编辑项目根目录下的 .env.local 文件以存储密钥
.env.local
GITHUB_ID=
GITHUB_SECRET=
AUTH_SECRET=

重要提示:请阅读 Qwik 文档中关于环境变量的部分,以确保您安全地使用它们。许多提供者密钥应保持安全,不要暴露给客户端/浏览器。

  1. 应用程序现在已准备好使用 Auth.js 实现身份验证。
  2. 尽情享受吧!

凭据

警告:Auth.js 不鼓励使用此功能。

https://next-auth.js.org/providers/credentials

  • 凭据身份验证提供的功能有意限制,以防止使用密码,因为密码本身存在固有的安全风险,并且支持用户名和密码会增加额外的复杂性。
  1. 由于默认的 plugin@auth.ts 使用 GitHub 作为示例,我们需要将其替换为凭据。
src/routes/plugin@auth.ts
import { serverAuth$ } from '@builder.io/qwik-auth';
import Credentials from "@auth/core/providers/credentials";
import type { Provider } from '@auth/core/providers';
 
export const { onRequest, useAuthSession, useAuthSignin, useAuthSignout } = serverAuth$(
  ({ env }) => ({
    secret: env.get("AUTH_SECRET"),
    trustHost: true,
    providers: [
      Credentials({
        async authorize(credentials, req) {
          // 在此处添加逻辑以查找提供的凭据对应的用户
          const user = {
            id: 1,
            name: "Mike",
            email: "mike@example.com",
          };
 
          return user;
        },
      }),
    ] as Provider[],
  })
);
  1. 创建或编辑项目根目录下的 .env.local 文件以存储密钥
.env.local
AUTH_SECRET=

重要提示:请阅读 Qwik 文档中关于环境变量的部分,以确保您安全地使用它们。许多提供者密钥应保持安全,不要暴露给客户端/浏览器。

  1. 应用程序现在已准备好使用 Auth.js 实现身份验证。
  2. 尽情享受吧!

路由保护

会话数据可以通过路由 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?callbackUrl=${event.url.pathname}`);
  }
};

注意:如果放置在 layout.tsx 中,请确保重定向目标不与相同的 layout.tsx 共享,否则可能会发生重定向循环。

Contributors

Thanks to all the contributors who have helped make this documentation better!

  • the-r3aper7
  • ulic75
  • jakovljevic-mladen
  • VarPDev