-
-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Tokens rotation does not persist the new token #7558
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
similiar problem is stated in #6642 . Sadly it seems like noone cares about this issue atm, altough its a system breaking problem. |
We cannot recreate the issue with the provided information. Please add a reproduction in order for us to be able to investigate. Why was this issue marked with the
|
Hello @balazsorban44. I've deployed Keycloak, made necessary setup and pushed my example based on the latest example repo fork to github. Alternatively, I've updated the main question, section Reproduction URL and How to reproduce You can find the credentials to login in the .env.local.example file, along with other necessary env variables, so just transfer this file to the .env.local file. Updated issue descriptionI've found more details. Look at this file in my repo. Tokens are rotating fine if you will comment lines 18 ... 25
|
@Mikk36 I disagree. As the author of referred discussion the problem is described with JWT tokens and not database strategy and problem seems to remain also we newer versions (its true that i haven't tested with latest version). |
My bad, I mixed up discussion numbers and thought it was something else. |
@mrbodich Instead of using
|
I have tried this and getting such error on backend |
@mrbodich That is not related to Please check PR - mrbodich/next-auth-example-fork#1 for detailed code. |
Thank you @anampartho, I got the idea now. Just was confused why it worked without server session handling. Can I ask you to give me a very important tip that I can't understand please? How can I use getServerSession not only in the root '/' route (or on each route separately), but on the very top level so all routes will inherit that? And how is it possible to add extra properties on the lower levels or sub-routes? Adding |
By the way, I came up with this solution. Updated provider's
PS: Thank you so much for your help. |
@mrbodich Unfortunately, you have to use |
I am using getServerSession in the app directory, but the problem stil occurs. But I guess the problem is because of the following issue stated by this user: #6642 (comment) |
@osmandvc Do you have a workaround for that? |
Sadly I did not find a really convenient way without too much overhead to make it work with RSC. The only solution currently seems like to switch to traditional client-side Authentication with useSession and a SessionProvider. |
Could you give me an example? |
I also have this problem with Since sveltekit does prefetching when hovering links it triggers an awful lot of "token refreshes" after the first access token expires. |
I use keycloack and I manage to make the token refresh a few seconds before it expires but the getsesion still gets the old token but when I refresh the browser tab with f5 it gets the refreshed token, I am trying to do something to observe this change, like a useeffect. import NextAuth, { KeycloakTokenSet, NextAuthOptions } from "next-auth";
import { JWT } from "next-auth/jwt";
import KeycloakProvider from "next-auth/providers/keycloak";
const keycloak = KeycloakProvider({
clientId: process.env.KEYCLOAK_ID,
clientSecret: process.env.KEYCLOAK_SECRET,
issuer: process.env.KEYCLOAK_ISSUER,
authorization: { params: { scope: "openid email profile offline_access" } },
});
async function doFinalSignoutHandshake(token: JWT) {
if (token.provider == keycloak.id) {
try {
const issuerUrl = keycloak.options!.issuer!;
const logOutUrl = new URL(`${issuerUrl}/protocol/openid-connect/logout`);
logOutUrl.searchParams.set("id_token_hint", token.id_token);
const { status, statusText } = await fetch(logOutUrl);
console.log("Completed post-logout handshake", status, statusText);
} catch (e: any) {
console.error("Unable to perform post-logout handshake", e?.code || e);
}
}
}
function parseJwt(token: string) {
return JSON.parse(Buffer.from(token.split(".")[1], "base64").toString());
}
async function refreshAccessToken(token: JWT): Promise<JWT> {
try {
// We need the `token_endpoint`.
const response = await fetch(
`${keycloak.options!.issuer}/protocol/openid-connect/token`,
{
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
client_id: keycloak.options!.clientId,
client_secret: keycloak.options!.clientSecret,
grant_type: "refresh_token",
refresh_token: token.refresh_token,
}),
method: "POST",
}
);
const tokensRaw = await response.json();
const tokens: KeycloakTokenSet = tokensRaw;
// console.log(tokensRaw)
if (!response.ok) throw tokens;
const expiresAt = Math.floor(Date.now() / 1000 + tokens.expires_in);
console.log(
`Token was refreshed. New token expires in ${tokens.expires_in} sec at ${expiresAt}, refresh token expires in ${tokens.refresh_expires_in} sec`
);
const newToken: JWT = {
...token,
access_token: tokens.access_token,
refresh_token: tokens.refresh_token,
id_token: tokens.id_token,
expires_at: expiresAt,
provider: keycloak.id,
};
return newToken;
} catch (error) {
// console.error("Error refreshing access token: ", error)
console.error("Error refreshing access token: ");
throw error;
}
}
// For more information on each option (and a full list of options) go to https://next-auth.js.org/configuration/options
export const authOptions: NextAuthOptions = {
secret: process.env.NEXTAUTH_SECRET,
// https://next-auth.js.org/configuration/providers/oauth
providers: [keycloak],
theme: {
colorScheme: "light",
},
pages: {
signIn: "/login",
signOut: "/login",
},
callbacks: {
async jwt({ token, account, user }) {
console.log("Executing jwt()");
if (account && user) {
const jwtDecoded = parseJwt(account.access_token as string);
if (!account.access_token)
throw Error("Auth Provider missing access token");
if (!account.refresh_token)
throw Error("Auth Provider missing refresh token");
if (!account.id_token) throw Error("Auth Provider missing ID token");
// Save the access token and refresh token in the JWT on the initial login
const newToken: JWT = {
...token,
access_token: account.access_token,
refresh_token: account.refresh_token,
id_token: account.id_token,
expires_at: Math.floor(account.expires_at ?? 0),
provider: account.provider,
userName: jwtDecoded.preferred_username,
userRoles: jwtDecoded.resource_access.account.roles,
};
return newToken;
}
const timeRemaining = token.expires_at * 1000 - Date.now();
if (timeRemaining > 30000) {
// If the token's remaining time is greater than 30 seconds, return the current token
console.log(
`\n>>> ${timeRemaining / 1000} seconds left until token expires`
);
return token;
}
console.log(`\n>>> Old token expired`);
// If the access token has expired or will expire within 30 seconds, try to refresh it
const newToken = await refreshAccessToken(token);
console.log(`New token adquired: ${newToken.expires_at}`);
return token;
},
async session({ session, token }) {
console.log(`Executing session() with token ${token.expires_at}`);
// You need to set the user object properly in jwt callback,
// Error: Error serializing .session.user.image was occuring because
// session.user.image was undefined, it needs to be value || null
session.user = { ...token };
return { ...session };
},
},
events: {
signOut: async ({ session, token }) => doFinalSignoutHandshake(token),
},
jwt: {
// maxAge: 60, // 20 horas
maxAge: 32400, // 9h
},
session: {
// maxAge: 30 * 24 * 60 * 60, // 30 days : 2592000, same as in Keycloak
maxAge: 32400, // 9h
},
};
export default NextAuth(authOptions); API: import { NODE_ENV, uri } from "@/constants/environment-variables";
import axios, { AxiosInstance, AxiosRequestConfig } from "axios";
const axiosInstance = axios.create({
baseURL: uri[NODE_ENV],
});
async function getData() {
const res = await axios.get("/api/auth/session");
return res;
}
const setAuthorizationHeader = async (axiosInstance: AxiosInstance) => {
// You can try to get the access token that way
const session = await getData();
// Or you can try to use Next Auth function
// const session = getSession()
// if I'm on the /api/auth/session page and I press f5 to refresh the tab, the new access token is there,
// however, neither getData() nor getSession() remains the previous access token, they will only update,
// if I refresh the page. I believe that useeffect solves it but I still don't know how to do it
console.log(session);
if (session) {
const token = session.data.user.access_token;
console.log(session);
axiosInstance.interceptors.request.use((config) => {
config.headers.Authorization = `Bearer ${token}`;
return config;
});
}
};
setAuthorizationHeader(axiosInstance);
const api = (axios: AxiosInstance) => {
return {
get: function <T>(url: string, config: AxiosRequestConfig = {}) {
return axios.get<T>(url, config);
},
put: function <T>(
url: string,
body: unknown,
config: AxiosRequestConfig = {}
) {
return axios.put<T>(url, body, config);
},
post: function <T>(
url: string,
body: unknown,
config: AxiosRequestConfig = {}
) {
return axios.post<T>(url, body, config);
},
delete: function <T>(url: string, config: AxiosRequestConfig = {}) {
return axios.delete<T>(url, config);
},
};
};
export default api(axiosInstance); |
I did it similar to this comment: #5647 (comment) . Basically follow the Tutorial on the Nextauth-Page, the only thing that changes is where you put your SessionProvider. Make a seperate Client-Component put your Provider there, and wrap your Content in your Root-Layout with the newly created Client-Component. This way you make sure that the layout.tsx remains a Server-Component. Now every underlying page has Access to the Session-Object (mostly with delay, because client-side) |
As of [email protected] (experimental), this is still an occurring issue. The initial token is stored; however, going forward, updates made to it are never saved (at least not to the token that is provided within Due to this issue, refresh token rotation is in practice, not possible with auth.js :( Edit 1: Edit 2: However, when using something like As such, there is much that can be done currently, until Next makes it possible to set cookies sitewide. Edit 3: |
Hi @balazsorban44 , refresh token rotation seems to be broken. Do you know if there is a plan to fix this issue? Is someone looking into it? |
Have a look at the issue here: #4229. Managed to get it work using update() from the useSession hook. |
It seems, that token rotation actually theoretically works when using auth.js When using token rotation after a login I noticed, that after a redirect it calls Auth again on /api/auth/session. I am using qwik-auth so I can only compare to that. It seems, that qwik-auth makes an exception when manually calling /api/auth/session and does not set the 'set-cookie' after a successful response. Following I will describe what happens in qwik-auth, but I assume that the issue is similar with other implementations. The issue is, that the updated cookie is not received by the client, since qwik-auth works like this: Context: const actions: AuthAction[] = [
'providers',
'session',
'csrf',
'signin',
'signout',
'callback',
'verify-request',
'error',
]; onRequest: const onRequest = async (req: RequestEvent) => {
if (isServer) {
const prefix: string = '/api/auth';
const action = req.url.pathname.slice(prefix.length + 1).split('/')[0] as AuthAction;
const auth = await authOptions(req);
// We notice, that there is no action that is named like so and neither is the prefix present.
if (actions.includes(action) && req.url.pathname.startsWith(prefix + '/')) {
const res = await Auth(req.request, auth);
const cookie = res.headers.get('set-cookie');
if (cookie) {
req.headers.set('set-cookie', cookie);
res.headers.delete('set-cookie');
fixCookies(req);
}
throw req.send(res);
} else {
// So this gets triggered and getSessionData(...) does not set the cookies on response.
req.sharedMap.set('session', await getSessionData(req.request, auth));
}
}
}; The fix I did is here: https://github.com/BuilderIO/qwik/pull/4960/files |
Ok so I took some time just looking at the react code, but since I don't use react I cannot confirm. Hopefully somebody can confirm this for me: 1. Get Session gets called:
2. fetchData gets called:
If Probably triggered here:
|
So what's needed is a way to have the cookie being pushed on any request, not just |
Sounds like you can just use a middleware. On every request initiated in protected routes, fetch to |
@rinvii ofc that would work, but then you also have to call signin manually since after signin the jwt rotation gets triggered again since session is called and at that point you are building your own implementation of /next-auth/src/react/index.ts I am assuming that react/index.ts is the same like https://github.com/BuilderIO/qwik/blob/47c2d1e838e9f748b191e983dabb0bac476f8083/packages/qwik-auth/src/index.ts // Disclaimer: |
I'm using I've managed to solve this problem with wrapping the root layout with "use client";
import { SessionProvider } from "next-auth/react";
import React, { ReactNode } from "react";
export const SessionProviderWrapper = ({
children,
}: {
children?: ReactNode;
}) => {
return <SessionProvider>{children}</SessionProvider>;
}; import { getServerSession } from "next-auth/next";
import { Nav } from "@/app/ui/Nav/Nav";
import { SessionProviderWrapper } from "@/app/lib/Providers";
import { authOptions } from "@/auth";
export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const session = await getServerSession(authOptions);
return (
<SessionProviderWrapper>
<html lang="en">
<body>
<Nav isLoggedIn={!!session} />
{children}
</body>
</html>
</SessionProviderWrapper>
);
} |
This is also happening with nextjs 14 and the new authjs 5 beta - using only the client side it will work, but whenever the token is refreshed through the middleware it is not updated to the client! The next request then still uses the old session. |
Because of this, I gave up on using next auth. |
This is still a problem. There is no way to update the session on the server side |
@HenrikZabel I literally mentioned it here: #7558 (comment) And it is discussed in detail here: #9715 @jarosik10 @wodka @wonkyDD The point is that you don't ever need things like SessionProvider when using next-auth and the middleware trick to update the session on server side. In fact, when using Next.js server components, you should completely avoid any client-side functions such as useSession(). You only focus on the server side, refresh the tokens there, and pass the session from server components down to client-components as props:
I can guarantee you that this works when implemented correctly. This method is actually quite in line with Next.js's official documentation on authentication: https://nextjs.org/docs/pages/building-your-application/authentication. It's just crazy that we have to figure out the best way to do this ourselves. |
@NanningR I am using v5. I thought about just manually calling |
@NanningR next-auth docs (which are not great) shows that refresh token rotation should be handled inside jwt callback delcared in next-auth configuration. https://authjs.dev/guides/refresh-token-rotation I haven't tried to handle the rotation inside middleware yet. But yeah, i agree that looking for the right approach (with poor docs) is kinda annoying. |
@jarosik10 Refreshing in the callbacks does not work (yet) because of how Next.js with the app router works; the cookies() update() method can't set cookies directly on the server side. That is why the solution is to implement the refresh token rotation logic in middleware.ts, where requests and responses can be intercepted. When refreshing the tokens in the middleware, you need to use the same token form factor as in your callbacks (explained here: #9715 (reply in thread) and here: #9715 (reply in thread)). The 'official' docs are hopelessly unclear, which is also the case for the page you just sent me, even though it got updated recently. Before #9715 existed, we were discussing here: #8254. It then got converted into the new discussion by the official maintainer: For some reason, they refuse to be clear in the docs. I think they have just not found a clean way to implement this for server components, so they are silent about the issue. I recommend you try the middleware solution. It is a bit of a hassle to set up, but works flawlessly once implemented. In any case, if you refuse to use this solution, you can always go for the database strategy instead of the JWT strategy. When saved in a database, the session can be updated however and whenever you like. Hope this helps :) @HenrikZabel I'm not using v5 myself yet because it is still in beta and as such not ready for production. However, a bunch of people in the discussion I linked to have successfully implemented this using the new v5. |
People from SvelteKit ecosystem, here is how to rotate the token - https://blog.aakashgoplani.in/how-to-implement-refresh-token-rotation-in-sveltekitauth |
Here’s some advice forget next auth and b2c and switch to auth0 instead you won’t look back
Sent from Outlook for iOS<https://aka.ms/o0ukef>
…________________________________
From: Aakash Goplani ***@***.***>
Sent: Sunday, July 14, 2024 12:39:52 PM
To: nextauthjs/next-auth ***@***.***>
Cc: Ben Parr ***@***.***>; Comment ***@***.***>
Subject: Re: [nextauthjs/next-auth] Tokens rotation does not persist the new token (Issue #7558)
People from SvelteKit ecosystem, here is how to rotate the token - https://blog.aakashgoplani.in/how-to-implement-refresh-token-rotation-in-sveltekitauth
—
Reply to this email directly, view it on GitHub<#7558 (comment)>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/ASYAXNKRVFVYUZIHXA2BVBTZMJIPRAVCNFSM6AAAAAAYCAJZDOVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDEMRXGI4TOOJXGY>.
You are receiving this because you commented.Message ID: ***@***.***>
|
This is based on a problem with race conditions. the jwt call-back in a non-trivial app, also using middleware, the JWT that is being returned is not used by subsequent calls as it still has the original jwt in access. |
Any updates on this issue? Edit by maintainer bot: Comment was automatically minimized because it was considered unhelpful. (If you think this was by mistake, let us know). Please only comment if it adds context to the issue. If you want to express that you have the same problem, use the upvote 👍 on the issue description or subscribe to the issue for updates. Thanks! |
The same problem was also observed in 5.0.0-beta.22. |
I have th same issue at beta.25. Some update? |
Hello, some update? I have the issue |
This is February 2025, and this issue still hasn't been resolved. Even the suggested middleware workarounds aren't working. Any updates would be greatly appreciated! |
Hey all, after about a million different attempts to get this to work for my apps, I have finally come to a solution that mimics pretty much exactly what we would want in refresh token behavior, works with the latest NextAuth v5, keeps all token/session manipulation in the NextAuth control/ecosystem, keeps everything server side, and allows for manual refresh! This basically just triggers a background sign-in process which, wait for it.... actual updates the session. I've simply repurposed the initial authorize function to do what it seems to do best, actual get in there and update the damn session. The check is for 5 minutes before but change as needed. Perhaps you keep your main token valid for 7 days and refresh within 3 days of expiration, or what ever your use case is. Choose your window and change the time check. Send "?refreshToken=true" on any request you would like to trigger a manual refresh. (role/permission updates, subscription changes, etc..). It will remove that param before passing it onto the final redirect keeping intact any other search parameters that may have been on the original request path. I have tested this also in the case that a user tries to access a role protected route. Redirects protecting that role based route still trigger even though the refresh technically redirects to any URL. One potential option to full proof that would be to move it below redirect logic. Right now my backend denies fully expired tokens, but if your backend does not validate token lifetime you can remove the redirect to a logout page so it will always get new valid tokens. I have generated documentation for the workaround which is below. You should be able to copy and paste this into cursor and it have a pretty good idea of what it needs to implement based on your current implementation. Workaround for NextAuth v5 JWT Refresh Token Bug in Next.jsIntroductionNextAuth v5 has a well-documented bug where refreshed JWT access tokens fail to persist in the session when using the JWT strategy. Specifically, after a refresh token is used to obtain a new access token within the This documentation presents a practical workaround that keeps refresh logic within the NextAuth ecosystem, avoiding the need to manually craft session tokens. By modifying the sign-in process to support authentication with either email/password or access/refresh tokens, this solution enables the system to "re-sign in" the user when a token refresh is needed. This updates the session with new token values securely on the server side, ensuring the refresh token remains protected and never exposed to the client. Below, we outline the implementation steps, explain each component, and highlight the benefits of this approach. Solution OverviewThe workaround leverages NextAuth's
This approach ensures seamless token management while maintaining security and simplicity. Implementation StepsBelow are the detailed steps to implement this workaround in your Next.js application using NextAuth v5. Each section includes a concise explanation and a generic code snippet tailored for broad applicability. 1. Modify
|
Middleware workarounds definitely still work and are easy to implement...granted there's no reason for this to still be an active issue and the docs still misleadingly say token refreshes work in the callbacks. |
@david-lagrange, How are you? Dude, could you make the project available in some repository? I'm asking this because I tried to implement it here and the refresh only worked the first two times the realod occurs, from then on it doesn't work anymore. I was able to solve this by sending the user to another page and then returning to the original page after the tokens are updated, but this isn't providing a good experience for the application. |
Hi, so I just noticed on v5 docs https://authjs.dev/getting-started/installation that Step 3, item 3 says Add optional Middleware to keep the session alive, this will update the session expiry every time its called. ./middleware.ts
I don't know why nobody is saying it or I didn't came across it, but it turns out that if you add that to your middleware, you can have the refresh token funcitonality in I'm working with |
For me that was the only issue with next-auth as well. Also this is stated in authjs docs as well:
And it looks like this can only be solved by refreshing the token in the middleware. |
here is my attempt for refresh tokens using
import { jwtDecode } from "jwt-decode";
import NextAuth from "next-auth";
import Keycloak from "next-auth/providers/keycloak";
const keycloakIssuer = process.env.AUTH_KEYCLOAK_ISSUER;
const keycloakId = process.env.AUTH_KEYCLOAK_ID;
const keycloakSecret = process.env.AUTH_KEYCLOAK_SECRET;
const refreshTokens = async (refreshToken: string) => {
const res = await fetch(`${keycloakIssuer}/protocol/openid-connect/token`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
client_id: keycloakId,
client_secret: keycloakSecret,
grant_type: "refresh_token",
refresh_token: refreshToken,
}),
});
return res.json();
};
export const { auth, handlers, signIn, signOut } = NextAuth({
providers: [Keycloak],
callbacks: {
async jwt({ token, account, user }) {
if (account?.access_token && user) {
const decoded = jwtDecode<any>(account.access_token);
return {
...token,
accessToken: account.access_token,
refreshToken: account.refresh_token!,
keycloakExp: decoded.exp,
};
}
const currentTimestamp = Math.floor(Date.now() / 1000);
const secondsLeft = token.keycloakExp - currentTimestamp;
console.log(
`Time until token expiry: ${secondsLeft}s (exp: ${token.keycloakExp}, now: ${currentTimestamp})`,
);
if (secondsLeft > 60) return token;
try {
const tokens = await refreshTokens(token.refreshToken);
if (tokens.error) return { ...token, error: "RefreshTokenError" };
const decoded = jwtDecode<any>(tokens.access_token);
return {
...token,
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token || token.refreshToken,
keycloakExp: decoded.exp,
};
} catch {
return token;
}
},
async session({ session, token }) {
session.error = token.error;
session.accessToken = token.accessToken;
session.refreshToken = token.refreshToken;
return session;
},
},
});
import { type DefaultSession } from "next-auth";
import "next-auth/jwt";
type AuthError = "RefreshTokenError" | "AccessTokenError";
declare module "next-auth" {
interface Session {
error?: AuthError;
accessToken: string;
refreshToken: string;
user: {} & DefaultSession["user"];
}
}
declare module "next-auth/jwt" {
interface JWT {
error?: AuthError;
accessToken: string;
refreshToken: string;
keycloakExp: number;
}
}
"use client";
import { signOut, useSession } from "next-auth/react";
import { ReactNode } from "react";
export function AuthProvider(props: {children: ReactNode}) {
const { data: session } = useSession();
useEffect(() => {
if (session?.error !== "RefreshTokenError") return;
signOut();
}, [session?.error]);
return <>{props.children}</>;
} |
Is this
not an issue anymore ? Refresh logic in jwt or authorized will not refresh tokens if a user stays on the same page but there are still some requests to an external api which will return 401 when token expires. Will it? and by the way
this is for next-auth callbacks to be a part of the middleware I believe |
Usually, if the keycloack cookie expires, you need to log out because you need a cookie for the new session, so the requests not working is fine, which |
https://authjs.dev/guides/refresh-token-rotation:
|
@voyager5874 the mutex one doesn't seem to work either or my implementation is not correct. can you share a working implementation of using async-mutex? |
I didn't try mutex inside auth.js callbacks actually - I'm just using it inside api layer very similar to RTK query docs https://redux-toolkit.js.org/rtk-query/usage/customizing-queries#preventing-multiple-unauthorized-errors. |
Environment
When rotating tokens, new token is not stored and thus not reused, so token is lost. The old token still persists instead and used for all further iterations of current session.
Only initial token generated on login works and reused constantly.
I use Keycloak as the external IDP
Keycloak — 21.1.1
Nextjs — 13.4.2
Next-auth — 4.22.1
Node — 16.2.0, 19.9.0
Reproduction URL
https://github.com/mrbodich/next-auth-example-fork.git
Describe the issue
When I use
async jwt()
function incallbacks
section, I get the new token from external IDP successfully, create the new token object and return inasync jwt()
just like documentation says.Here is my piece of code in the last
else
block (if access token is expired)Once token expired, and
else
block is executed, I have constantly updating at each request. Here is what I get in the console logged:As you see,
1684147058
is not changed between requests, so new JWT is just lost somewhere and not used for later requests. Though at the first login, returned jwt is used correctly.How to reproduce
.env.local.example
file to.env.local file
.env.local.example
file,row 13
18 ... 25
in theindex.tsx
file (getServerSideProps function), and tokens will start rotating fine.Expected behavior
Token returned in the
async jwt()
function incallbacks
section must be used on the next request and not being lost.The text was updated successfully, but these errors were encountered: