当我在下一个身份验证的提供者中选择凭据时,如何同步下一个身份验证会话的过期时间和来自服务器的令牌

发布于 2025-01-11 20:57:50 字数 2552 浏览 2 评论 0原文

我已经为我的 Next.js 应用程序实现了 next-auth 身份验证系统。在提供程序中,我选择了凭据,因为我有一个 Node.js 后端服务器。

我面临的问题是下一个身份验证会话的过期与我后端的 jwt 令牌的过期不同步。这会导致不一致。请帮助我。

下面是我的下一个授权代码

import NextAuth, {
  NextAuthOptions,
  Session,
  SessionStrategy,
  User,
} from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import { login } from "@actions/auth";
import { toast } from "react-toastify";
import { JWT } from "next-auth/jwt";
import { NextApiRequest, NextApiResponse } from "next";
import { SessionToken } from "next-auth/core/lib/cookie";

// For more information on each option (and a full list of options) go to
// https://next-auth.js.org/configuration/options
const nextAuthOptions = (req: NextApiRequest, res: NextApiResponse) => {
  return {
    providers: [
      CredentialsProvider({
        name: "Credentials",
        credentials: {
          email: { label: "Email", type: "text" },
          password: { label: "Password", type: "password" },
        },
        async authorize(
          credentials: Record<"email" | "password", string> | undefined,
          req
        ): Promise<Omit<User, "id"> | { id?: string | undefined } | null> {
          // Add logic here to look up the user from the credentials supplied
          const response = await login(
            credentials?.email!,
            credentials?.password!
          );
          const cookies = response.headers["set-cookie"];

          res.setHeader("Set-Cookie", cookies);
          if (response) {
            var user = { token: response.data.token, data: response.data.user };
            return user;
          } else {
            return null;
          }
        },
      }),
    ],
    refetchInterval: 1 * 24 * 60 * 60,
    secret: process.env.NEXTAUTH_SECRET,
    debug: true,
    session: {
      strategy: "jwt" as SessionStrategy,
      maxAge: 3 * 24 * 60 * 60,
    },
    jwt: {
      maxAge: 3 * 24 * 60 * 60,
    },
    callbacks: {
      jwt: async ({ token, user }: { token: JWT; user?: User }) => {
        user && (token.accessToken = user.token);
        user && (token.user = user.data);
        return token;
      },
      session: async ({ session, token }: { session: Session; token: JWT }) => {
        session.user = token.user;
        session.accessToken = token.accessToken;
        return session;
      },
    },
  };
};
export default (req: NextApiRequest, res: NextApiResponse) => {
  return NextAuth(req, res, nextAuthOptions(req, res));
};

I have implemented a next-auth authentication system for my Next.js app. In the providers, I have chosen credentials because I have a node.js backend server.

The problem that I am facing is the expiration of next auth session is not in sync up with the expiration of jwt token on my backend. This is leading to inconsistency. Kindly help me out.

Below is my next auth code

import NextAuth, {
  NextAuthOptions,
  Session,
  SessionStrategy,
  User,
} from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import { login } from "@actions/auth";
import { toast } from "react-toastify";
import { JWT } from "next-auth/jwt";
import { NextApiRequest, NextApiResponse } from "next";
import { SessionToken } from "next-auth/core/lib/cookie";

// For more information on each option (and a full list of options) go to
// https://next-auth.js.org/configuration/options
const nextAuthOptions = (req: NextApiRequest, res: NextApiResponse) => {
  return {
    providers: [
      CredentialsProvider({
        name: "Credentials",
        credentials: {
          email: { label: "Email", type: "text" },
          password: { label: "Password", type: "password" },
        },
        async authorize(
          credentials: Record<"email" | "password", string> | undefined,
          req
        ): Promise<Omit<User, "id"> | { id?: string | undefined } | null> {
          // Add logic here to look up the user from the credentials supplied
          const response = await login(
            credentials?.email!,
            credentials?.password!
          );
          const cookies = response.headers["set-cookie"];

          res.setHeader("Set-Cookie", cookies);
          if (response) {
            var user = { token: response.data.token, data: response.data.user };
            return user;
          } else {
            return null;
          }
        },
      }),
    ],
    refetchInterval: 1 * 24 * 60 * 60,
    secret: process.env.NEXTAUTH_SECRET,
    debug: true,
    session: {
      strategy: "jwt" as SessionStrategy,
      maxAge: 3 * 24 * 60 * 60,
    },
    jwt: {
      maxAge: 3 * 24 * 60 * 60,
    },
    callbacks: {
      jwt: async ({ token, user }: { token: JWT; user?: User }) => {
        user && (token.accessToken = user.token);
        user && (token.user = user.data);
        return token;
      },
      session: async ({ session, token }: { session: Session; token: JWT }) => {
        session.user = token.user;
        session.accessToken = token.accessToken;
        return session;
      },
    },
  };
};
export default (req: NextApiRequest, res: NextApiResponse) => {
  return NextAuth(req, res, nextAuthOptions(req, res));
};

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

笑看君怀她人 2025-01-18 20:57:50

我有一个类似的设置:在客户端上使用带有 jwt 和单独后端会话令牌的凭证身份验证的 NextAuth(版本 4)和 Next.js(带有 App Router 的版本 13)。

这就是我们保持会话同步的方式:

  1. 正如其他人提到的,在 NextAuthOptions 中,设置 maxAge 属性设置为与后端服务器上的令牌相同的过期时间。

    const nextAuthOptions = {
      提供者:[...],
      会议: {
        策略:'jwt',
        maxAge: 4 * 60 * 60 // 4 小时
      },
      ...
    }
    
  2. 在经过身份验证的页面的路由树的顶层,检查您的客户端会话是否即将过期,如果是,则刷新令牌。我使用 NextAuth useSession update< 刷新客户端上的令牌/a> 函数并向后端 API 发送请求以更新服务器上的令牌过期时间。这将添加到顶层层次结构中的 layout.tsx 文件中,以便任何需要经过身份验证才能查看的视图。

    --layout.tsx--

    '使用客户端';
    从'next-auth/react'导入{useSession};
    
    导出默认函数 Layout() {
      const { 数据:会话、状态、更新 } = useSession();
    
      useEffect(() => {
        const 间隔 = setInterval(() => {
          更新(); // 扩展客户端会话
          // TODO 请求服务器刷新令牌
        }, 1000 * 60 * 60)
        返回() =>清除间隔(间隔)
      }, [更新]); 
      返回 (
        {孩子们}
      )
    }
    

  3. 如果您还想添加功能来确定用户在延长会话之前是否处于空闲状态,您可以使用 反应空闲计时器

    --完整的layout.tsx文件--

    '使用客户端';
    从 'react' 导入 React, { useEffect };
    从'next-auth/react'导入{useSession,signOut};
    从'react-idle-timer'导入{useIdleTimer};
    
    导出默认函数Layout({children}:{children:React.ReactNode 
    }) {
      const { 数据:会话、状态、更新 } = useSession();
      常量 CHECK_SESSION_EXP_TIME = 300000; // 5 分钟
      常量 SESSION_IDLE_TIME = 300000; // 5 分钟 
      const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL;
    
      const onUserIdle = () =>; {
        console.log('空闲');
      };
    
      const onUserActive = () =>; {
        console.log('活动');
      };
    
      const { isIdle } = useIdleTimer({
        onIdle: onUserIdle,
        onActive: onUserActive,
        超时: SESSION_IDLE_TIME, //毫秒
        油门:500
      });
    
      useEffect(() => {
        const checkUserSession = setInterval(() => {
          const expiresTimeTimestamp = Math.floor(new Date(session?.expires || '').getTime());
          const currentTimestamp = Date.now();
          const timeRemaining = expiresTimeTimestamp - currentTimestamp;
    
          // 如果用户会话将在下一次会话检查之前过期
          // 并且用户不空闲,那么我们要刷新会话
          // 在客户端并在后端请求令牌刷新
          if (!isIdle() && 剩余时间 < CHECK_SESSION_EXP_TIME) {
            更新(); // 延长客户端会话
    
            // 在这里请求刷新后端令牌
    
          } else if (剩余时间 < 0) {
            // 会话已过期,注销用户并显示会话过期消息
            SignOut({callbackUrl: BASE_URL + '/login?error=SessionExpired' });
          }
        }、CHECK_SESSION_EXP_TIME);
    
        返回() => {
          清除间隔(检查用户会话);
        };
      }, [更新]); 
      返回 (
        <主要>
          {孩子们}
        
      );
    }
    

I have a similar setup: NextAuth (version 4) with Next.js (version 13 with App Router) on the client using credential authentication with a jwt and a separate backend session token.

This is how we keep the sessions in sync:

  1. As others mentioned, in the NextAuthOptions, set the maxAge property to the same expiration time as the token on the back end server.

    const nextAuthOptions = {
      providers: [...],
      session: {
        strategy: 'jwt',
        maxAge: 4 * 60 * 60 // 4 hours
      },
      ...
    }
    
  2. At the top level of the route tree for your authenticated pages, check if your client-side session is about to expire and if so, refresh the token. I refresh the token on the client-side with the NextAuth useSession update function and send a request to the backend API to update the token expiration on the server. This is added to the layout.tsx file in the top level hierarchy for any views that should be authenticated to see.

    --layout.tsx--

    'use client';
    import { useSession } from 'next-auth/react';
    
    export default function Layout() {
      const { data: session, status, update } = useSession();
    
      useEffect(() => {
        const interval = setInterval(() => {
          update(); // extend client session
          // TODO request token refresh from server
        }, 1000 * 60 * 60)
        return () => clearInterval(interval)
      }, [update]); 
      return (
        {children}
      )
    }
    
  3. If you also want to add functionality to determine if the user is idle or not before extending their session, you can use react-idle-timer.

    --full layout.tsx file--

    'use client';
    import React, { useEffect } from 'react';
    import { useSession, signOut } from 'next-auth/react';
    import { useIdleTimer } from 'react-idle-timer';
    
    export default function Layout({ children }: { children: React.ReactNode 
    }) {
      const { data: session, status, update } = useSession();
      const CHECK_SESSION_EXP_TIME = 300000; // 5 mins
      const SESSION_IDLE_TIME = 300000; // 5 mins 
      const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL;
    
      const onUserIdle = () => {
        console.log('IDLE');
      };
    
      const onUserActive = () => {
        console.log('ACTIVE');
      };
    
      const { isIdle } = useIdleTimer({
        onIdle: onUserIdle,
        onActive: onUserActive,
        timeout: SESSION_IDLE_TIME, //milliseconds
        throttle: 500
      });
    
      useEffect(() => {
        const checkUserSession = setInterval(() => {
          const expiresTimeTimestamp = Math.floor(new Date(session?.expires || '').getTime());
          const currentTimestamp = Date.now();
          const timeRemaining = expiresTimeTimestamp - currentTimestamp;
    
          // If the user session will expire before the next session check
          // and the user is not idle, then we want to refresh the session
          // on the client and request a token refresh on the backend
          if (!isIdle() && timeRemaining < CHECK_SESSION_EXP_TIME) {
            update(); // extend the client session
    
            // request refresh of backend token here
    
          } else if (timeRemaining < 0) {
            // session has expired, logout the user and display session expiration message
            signOut({ callbackUrl: BASE_URL + '/login?error=SessionExpired' });
          }
        }, CHECK_SESSION_EXP_TIME);
    
        return () => {
          clearInterval(checkUserSession);
        };
      }, [update]); 
      return (
        <main>
          {children}
        </main>
      );
    }
    
巴黎盛开的樱花 2025-01-18 20:57:50

在您的选项中,有 maxAge 属性。将其设置为等于您在后端服务器中设置的任何时间。时间以秒为单位,因此您当前设置为 3 天。

请参阅此处

In your options, there is the maxAge property. Set it to be equal to whatever time you have set in your backend server. The time is in seconds, so yours is currently set to 3days.

See here

帥小哥 2025-01-18 20:57:50

不确定这是否对任何人有帮助,但我无法弄清楚如何阻止应用程序在每次闲置或离开页面并返回时发出新令牌,因此我放弃了尝试同步它们。相反,我只是将它们作为附加属性包含在令牌中。所以我的中间件可以知道令牌何时过期。

import { getToken } from 'next-auth/jwt';
import { NextResponse } from 'next/server';
import { DateTime } from 'luxon';

export async function middleware(req) {
  const token = await getToken({ req, secret: process.env.JWT_SECRET });
  const { pathname } = req.nextUrl;
  const origin = req.nextUrl.origin;

  if (pathname === '/auth/sign-in') {
    return NextResponse.next();
  }

  const isTokenExpired =
    token?.apiExp && DateTime.fromSeconds(token.apiExp) < DateTime.now();

  const isPublicRoute =
    pathname.includes('/api/auth') ||
    pathname.includes('.png') ||
    pathname.includes('.svg') ||
    pathname.includes('/favicon.ico') ||
    pathname.includes('jpg') ||
    pathname.includes('_next');

  if (isPublicRoute) {
    return NextResponse.next();
  }

  if (!token || isTokenExpired) {
    console.log('Not signed in or token expired, redirecting to signIn');
    return NextResponse.redirect(origin + '/auth/sign-in');
  }

  return NextResponse.next();
}
in [...nextauth]

const callbacks = {
  async jwt({ token, user }) {
    if (user) {
      token.id = user.id;
      token.email = user.email;
      token.accountId = user.accountId;
      token.apiIat = user.iat;
      token.apiExp = user.exp;
    }
    return token;
  },
Token {
  email: '[email protected]',
  sub: '2',
  id: 2,
  accountId: 1,
  apiIat: 1694127090,
  apiExp: 1694130690,
  iat: 1694127090,
  exp: 1694130690,
  jti: '82342349c-e222a-409a-b91f-c00327367d0f'
}
Token {
  email: '[email protected]',
  sub: '2',
  id: 2,
  accountId: 1,
  apiIat: 1694127090,
  apiExp: 1694130690,
  iat: 1694127094,
  exp: 1696719094,
  jti: '123456-1234-1234-1234-1234567890'
}

Not sure if this helps anyone, but I could not figure out how to keep the app from issuing new tokens every time I idled or left the page and came back, so I gave up on trying to sync them. And instead am just including them in the token as additional properties. So my middleware can know when the token is expired.

import { getToken } from 'next-auth/jwt';
import { NextResponse } from 'next/server';
import { DateTime } from 'luxon';

export async function middleware(req) {
  const token = await getToken({ req, secret: process.env.JWT_SECRET });
  const { pathname } = req.nextUrl;
  const origin = req.nextUrl.origin;

  if (pathname === '/auth/sign-in') {
    return NextResponse.next();
  }

  const isTokenExpired =
    token?.apiExp && DateTime.fromSeconds(token.apiExp) < DateTime.now();

  const isPublicRoute =
    pathname.includes('/api/auth') ||
    pathname.includes('.png') ||
    pathname.includes('.svg') ||
    pathname.includes('/favicon.ico') ||
    pathname.includes('jpg') ||
    pathname.includes('_next');

  if (isPublicRoute) {
    return NextResponse.next();
  }

  if (!token || isTokenExpired) {
    console.log('Not signed in or token expired, redirecting to signIn');
    return NextResponse.redirect(origin + '/auth/sign-in');
  }

  return NextResponse.next();
}
in [...nextauth]

const callbacks = {
  async jwt({ token, user }) {
    if (user) {
      token.id = user.id;
      token.email = user.email;
      token.accountId = user.accountId;
      token.apiIat = user.iat;
      token.apiExp = user.exp;
    }
    return token;
  },
Token {
  email: '[email protected]',
  sub: '2',
  id: 2,
  accountId: 1,
  apiIat: 1694127090,
  apiExp: 1694130690,
  iat: 1694127090,
  exp: 1694130690,
  jti: '82342349c-e222a-409a-b91f-c00327367d0f'
}
Token {
  email: '[email protected]',
  sub: '2',
  id: 2,
  accountId: 1,
  apiIat: 1694127090,
  apiExp: 1694130690,
  iat: 1694127094,
  exp: 1696719094,
  jti: '123456-1234-1234-1234-1234567890'
}
甜宝宝 2025-01-18 20:57:50

我找到了一个解决方案,您可以尝试将 jwt cookie 保存在会话令牌内并从那里读取它,它们将同时过期
查看此以获取更多信息 https://github.com/nextauthjs/next-auth/讨论/1290

I found a solution you can try to save the jwt cookie inside session token and read it from there and they will expiry both at the same time
check this out for more info https://github.com/nextauthjs/next-auth/discussions/1290

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文