首页 > 其他分享 >NextJS - 使用 next-auth 配置 JWT token

NextJS - 使用 next-auth 配置 JWT token

时间:2023-08-11 13:13:07浏览次数:38  
标签:... return JWT auth next token session

Nextjs 中有很多身份验证选项,例如 Supabase、Firebase、Userbase 等等。 我们将重点关注 NextAuth.js 以及通过凭证提供程序在现有 Django 后端和 Next.js 之间实现 JWT 会话的打字稿。 我们将尽力专注于我们的用例以节省时间,因此我们将省略所有未使用的选项和功能。

为什么选择 NextAuth.js

这个全栈库可以帮助您与每个主要的 OAuth 提供商集成,也可以仅与电子邮件身份验证集成(推荐这样做,因为安全性更高)。 如果需要,您还可以创建自定义 OAuth 或凭据提供程序。 这就是我们喜欢这个库的原因,它为您提供了通用流程和默认处理程序,但您可以轻松重写每个步骤以满足您的需求。

最简配置

通过命令 yarn add next-auth 安装软件包后,或者如果您更喜欢 npm install next-auth,则必须创建配置 [...nextauth].ts 文件,该文件将位于 API 路由 /api/auth/[... nextauth].ts
这意味着所有到达 /api/auth/* 的请求都将由 NextAuth.js 处理。 在此文件中,我们将导出处理程序函数,其中将包含我们的配置。 您可以在此处找到有关配置的更多详细信息。


// pages/api/auth/[...nextauth].ts

export default async function auth(
      req: NextApiRequest, 
      res: NextApiResponse
) {​
    return await NextAuth(req, res, {​
        providers: [ ... ],​
        session: {​
            strategy: "jwt",​
        },​
        cookies: cookies,​
        callbacks: { ... },​
    });​
}

一般情况下,当您想要使用 JWT 会话时,必须将 session.strategy 设置为 jwt 并指定用于加密令牌的密钥。
我们建议您通过环境变量 NEXTAUTH_SECRET 设置秘密。 此外,如果您不部署到 Vercel,则需要将站点的规范 URL 作为 NEXTAUTH_URL。

在我们的示例中,我们将使用 session-tokencallback-urlcsrf-token。 它们分别用于存储 JWT 令牌、登录/退出后将重定向的默认回调以及最后的 CSRF 令牌。
要使我们的令牌在所有子域中可用,您必须将 cookie 的域选项设置为有效域,例如 如果您的域是 account.example.com 和 example.com,则必须将域选项设置为 example.com。


// pages/api/auth/[...nextauth].ts

const cookies: Partial<CookiesOptions> = {​
    sessionToken: {​
        name: `next-auth.session-token`,​
        options: {​
            httpOnly: true,​
            sameSite: "none",​
            path: "/",​
            domain: process.env.NEXT_PUBLIC_DOMAIN,​
            secure: true,​
        },​
    },​
    callbackUrl: {​
        name: `next-auth.callback-url`,​
        options: {​
            ...​
        },
    },​
    csrfToken: {​
        name: "next-auth.csrf-token",​
        options: {​
        ...​
        },​
    },​
};

类型安全

NextAuth.js 提供内置类型,但由于我们需要在会话对象中存储更多信息,因此我们必须重写 SessionUserJWT 对象的类型。
在文档中阅读有关 cookie 及其选项的更多信息。
https://next-auth.js.org/configuration/options#cookies

// types/next-auth.d.ts

declare module "next-auth" {
  /**
   * Returned by `useSession`, `getSession` and received as
   * a prop on the `SessionProvider` React Context
   */
  interface Session {
    refreshTokenExpires?: number;
    accessTokenExpires?: string;
    refreshToken?: string;
    token?: string;
    error?: string;
    user?: User;
  }

  interface User {
    firstName?: string;
    lastName?: string;
    email?: string | null;
    id?: string;
    contactAddress?: {
      id?: string;
    };
  }
}

declare module "next-auth/jwt" {
  /** Returned by the `jwt` callback and `getToken`, when using JWT sessions */
  interface JWT {
    refreshTokenExpires?: number;
    accessTokenExpires?: number;
    refreshToken?: string;
    token: string;
    exp?: number;
    iat?: number;
    jti?: string;
  }
}

认证流程

凭证提供者(Credentials provider)

我们终于可以将凭据提供程序添加到我们的配置中。 需要提供者的nameid来区分不同的提供者。 credentials对象表示登录表单的字段,该对象结构将作为authorize函数的第一个参数传递。

// pages/api/auth/[...nextauth].ts

providers: [​
    Providers.Credentials({​
        name: 'credentials',​
        id: 'credentials',​
        credentials: {​
            username: { },​
            password: { }​
        },​
        async authorize(credentials, req) {​
            // ...​
        }​
    })​
],

authorize函数负责从自定义后端实现中获取用户,该实现应该为我们提供 JWT 令牌。

// pages/api/auth/[...nextauth].ts

async authorize(credentials) {​
    const response = await fetch('...', {​
        ...​
        variables: {​
            email: credentials?.email,​
            password: credentials?.password,​
        },​
    });​
    const data = await response.json();​
    if (response.ok && data?.token) {​
        return  data;
    }​
    return Promise.reject(new Error(data?.errors));​​
};

回调 (Callbacks)

我们流程中的第一个回调是 jwt。 每当客户端创建或访问 JSON Web Token 时都会调用此回调。 这是实施代币轮换的正确位置。 freshAccessToken 函数的目的是使用存储在 token 对象中的刷新令牌,并使用它来获取具有更新的过期时间的新访问令牌。 请注意,我们的后端为我们提供了以秒为单位的过期时间,而 Date.now() 的输出以毫秒为单位,这就是为什么我们需要将其除以 1000。

// pages/api/auth/[...nextauth].ts

export const jwt = async ({ token, user }: { token: JWT; user?: User }) => {
  // first call of jwt function just user object is provided
  if (user?.email) {
    return { ...token, ...user };
  }

  // on subsequent calls, token is provided and we need to check if it's expired
  if (token?.accessTokenExpires) {
    if (Date.now() / 1000 < token?.accessTokenExpires) return { ...token, ...user };
  } else if (token?.refreshToken) return refreshAccessToken(token);

  return { ...token, ...user };
};

jwt 的输出作为 token 传递到 session 回调中。 这是将附加数据传递到 session 对象的好地方。
在我们的例子中,我们将解析后端在 token 内向我们提供的所有数据,并将其作为 Web 客户端的 user 对象传递。 此外,我们还可以检查访问令牌和刷新令牌是否已过期并引发错误。

// pages/api/auth/[...nextauth].ts

export const session = ({ session, token }: { session: Session; token: JWT })
 : Promise<Session> => {

  if (Date.now() / 1000 > token?.accessTokenExpires && 
      token?.refreshTokenExpires && Date.now() / 1000 > token?.refreshTokenExpires) {
    return Promise.reject({
      error: new Error("Refresh token has expired. Please log in again to get a new refresh token."),
    });
  }

  const accessTokenData = JSON.parse(atob(token.token.split(".")?.at(1)));
  session.user = accessTokenData;
  token.accessTokenExpires = accessTokenData.exp;

  session.token = token?.token;

  return Promise.resolve(session);
};

这是我们的身份验证流程的可视化。

客户端使用

正如我们之前提到的,我们使用自定义登录页面,以便在客户端验证后我们可以调用 signIn。 将重定向设置为 false 后,我们可以手动处理错误和成功。

// pages/login.tsx

signIn("credentials", {
  username: data?.username,
  password: data?.password,
  redirect: false,
}).then((response) => {
  if (response?.error) {
    // show notification for user
  } else {
    // redirect to destination page
  }
});

如果我们不需要验证响应,我们可以将重定向设置为 true 并提供回调 URL,以便在用户注销后他将被重定向到指定页面。

// pages/logout.tsx

signOut({
  redirect: true,
  callbackUrl: `${process?.env.NEXT_PUBLIC_LOGIN_PAGE}/login`,
});

当您需要访问 session 数据或访问客户端中的 token 令牌时,可以使用 useSession() 钩子。 在我们的例子中,我们将使用自定义属性获取session类型。

中间件 (Middleware)

如果您使用 Next.js 12 或更高版本,则可以在中间件 middleware 中使用 NextAuth.js。 在基本用法中,我们只需导出一个 matcher 对象,其中包含我们想要保护的路径名数组。

// middleware.ts

export { default } from "next-auth/middleware"

export const config = { matcher: [ ... ] }

如果您需要一些高级逻辑,您可以使用自定义 middleware 中间件实现。 我们可以通过 getToken() 访问和解码中间件中的令牌数据。
例如,如果用户没有管理员访问权限,我们可以重定向用户。


// middleware.ts

export async function middleware(request: NextRequest) {
  const token = await getToken({
    req: request,
    secret: process?.env?.NEXTAUTH_SECRET,
    cookieName: ACCESS_TOKEN, // next-auth.session-token
  });

  // redirect user without access to login
  if (token?.token && Date.now() / 1000 < token?.accessTokenExpires) {
    return NextResponse.redirect("/login");
  }

  // redirect user without admin access to login
  if (!token?.isAdmin) {
    return NextResponse.redirect("/login");
  }

  return NextResponse.next();
}

子域名设置

当您只有一个域名时,到目前为止的所有内容都对您有效。 恭喜你,你已经完成了。 其余的已成功完成负责身份验证的域的实施。 在撰写本文时,NextAuth.js 文档尚未提供官方解决方案。 我们可以发现这里只是我们需要设置一个自定义的cookie策略。

我们需要在所有子域之间共享除 authorize 授权功能之外的所有内容。 这是有道理的,因为我们永远不会从子域调用 signIn

// pages/api/auth/[...nextauth].ts

export default NextAuth({
  providers: [
    CredentialsProvider({
      name: "id",
      name: "credentials",
      credentials: {},
      async authorize(_credentials, _req) {
        return null;
      },
    }),
  ],
  session: {
    strategy: "jwt",
  },
  cookies: cookies,
  callbacks: {
    session,
    jwt,
  },
});

因此,我们需要共享 cookiesessionjwt 回调的设置,以同样地访问和刷新令牌。 这就是为什么我们建议您将这些功能移至共享模块中,以便您可以从两个站点访问它们。 不要忘记使用相同的 secret,因为 token 令牌的解密将会失败。

ref: https://remaster.com/blog/next-auth-jwt-session

标签:...,return,JWT,auth,next,token,session
From: https://www.cnblogs.com/eddyz/p/17622721.html

相关文章

  • 操作过滤器—MVC中使用操作过滤器实现JWT权限认证
    前言上一篇文章分享了授权过滤器实现JWT进行鉴权,文章链接:授权过滤器—MVC中使用授权过滤器实现JWT权限认证,接下来将用操作过滤器实现昨天的JWT鉴权。一、什么是操作过滤器?​与授权过滤器大部分一样,只是执行的时机和继承的接口有所不同。操作过滤器是在Action执行的前和后进......
  • SpringSecurity -- 授权-OAuth2 --
    前言针对之前的授权做个补充,这里集成OAuth2来实现目前支持5种方式,inMemory,jdbc,redis,jwt,jwk对于公司内部使用呢,主推jwt>redis>jdbc>inMemory​ 。。。jwk没玩明白,但是安全性挺高。。。整明白了,觉得可以主推!!!而认证类型呢,也有5种,authorization_code,refresh_token,password......
  • Next.js - App Router Vs. Pages Router 详细对比
    多年来,我们将页面放置在Next的“pages”目录中。现在这种情况即将改变。不久前,Next.js推出了新的AppRouter,显着改变了我们创建页面的方式。但不仅是我们存储应用程序页面的目录发生了变化,而且可用的功能也发生了变化。我们的下一个项目过去是这样的:└──pages├......
  • 链接Mongodb报错Unable to authenticate using sasl protocol mechanism SCRAM-SHA-1
    解决方案在连接数据库字符串后面添加authSource=admin  "MongoDB":{"Host":"mongodb://touchadmin:[email protected]:27017/TouchAdmin?authSource=admin","DbName":"TouchAdmin"} useTouchAdmindb.cre......
  • abp-vnext-pro 实战(六,vue 前端状态pinia)
    在login的时候把所有写入全局store,console.log('----------------从数据库获取字典--------------------');constappStore=useAppStore();constdataDictionaryServiceProxy=newDataDictionaryServiceProxy();constdetailInput=new......
  • 【Azure ACR+App Service】ACR WebHook请求App Service时遇见 401 Unauthorized
    问题描述AppService支持从ACR中直接拉取镜像,并且可以配置持续部署(ContinuousDeployment),它是通过在ACR中添加一个Webhook,然后发送POST请求到<yourappservicename>.scm.chinacloudsites.cn/api/registry/webhook接口,触发AppService从ACR中pull新的image。但是,为什么会......
  • SSH连接问题“No supported authentication methods available”
    SSH连接问题1.问题描述:  接到同事上报,在使用Putty登录远程服务器时出现如下问题,“Nosupportedauthenticationmethodsavailable”详情如图。  通过沟通得知,服务器最初提供的认证方式为密钥登录,为了方便使用想改为密码登录,并且同事已经对/etc/ssh/sshd_config配置文件进......
  • Python迭代器的__iter__和__next__详细教程
    在Python中,迭代器是一个实现了__iter__和__next__方法的对象。__iter__方法返回迭代器对象自身,而__next__方法返回下一个元素。换句话说,迭代器是一个可以逐个返回元素的对象。下面是一个简单的迭代器示例,演示了如何实现__iter__和__next__方法:classMyIterator:d......
  • Python迭代器的__iter__和__next__详细教程
    在Python中,迭代器是一个实现了__iter__和__next__方法的对象。__iter__方法返回迭代器对象自身,而__next__方法返回下一个元素。换句话说,迭代器是一个可以逐个返回元素的对象。下面是一个简单的迭代器示例,演示了如何实现__iter__和__next__方法:classMyIterator:......
  • Abp vNext单点登录
    AbpvNext单点登录使用AbpvNext6.0分析AbpvNext说OpenIddict是支持单点登录的,不过我找不到相关内容OpenIddictmoduleprovidesanintegrationwiththeOpenIddictwhichprovidesadvancedauthenticationfeatureslikesinglesign-on,singlelog-out,andAPIacces......