auth0 / nextjs-auth0

Next.js SDK for signing in with Auth0
MIT License
2.06k stars 389 forks source link

Support async API required by Next 15 #1779

Open Enngage opened 2 weeks ago

Enngage commented 2 weeks ago

Checklist

Describe the problem you'd like to have solved

Accessing cookies / headers is now async and the app fails if you access it synchronously in latest canary versions of next 15. More about the change is available within their docs - https://nextjs.org/docs/messages/sync-dynamic-apis

Describe the ideal solution

Implement async handling of cookies - https://nextjs.org/docs/messages/sync-dynamic-apis

Alternatives and current workarounds

There are no workarounds right now, other then staying on older versions of Next 15. Yeah, it's still a Canary version, but we expect to release it soon.

Additional context

No response

tpiros commented 1 week ago

Next.js is now officially live: https://nextjs.org/blog/next-15. It'd be great to have a fix for this as it's now breaking my authentication flow.

alioftech commented 1 week ago

Fix please. Can't upgrade to nextjs 15 before fix

ciocanraul22 commented 1 week ago

I see that this issue is already in-view, then I wasted my toime posting on Vercel's github about the issue.

Just for reference: here's what I get in the console: Error: In route /api/auth/[auth0] a param property was accessed directly withparams.auth0.paramsshould be awaited before accessing its properties. Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis

guabu commented 1 week ago

Hi all πŸ‘‹ We're working on adding support for the dynamic APIs in the upcoming v4 release of the SDK.

Enngage commented 1 week ago

Hi all πŸ‘‹ We're working on adding support for the dynamic APIs in the upcoming v4 release of the SDK.

That's great to hear @guabu :) Do you by any chance have an estimation of when beta/release will be available?

guabu commented 1 week ago

That's great to hear @guabu :) Do you by any chance have an estimation of when beta/release will be available?

We're working to release the alpha version today and expect to release the beta shortly after (a few days or a couple of weeks at most).

BogdanManescu commented 1 week ago

Next 15 introduced some new breaking! updates, i fixed authentication easily, hint: use await

steelbrain commented 1 week ago

Hi all πŸ‘‹ We're working on adding support for the dynamic APIs in the upcoming v4 release of the SDK.

That's great to hear @guabu :) Do you by any chance have an estimation of when beta/release will be available?

I understand that we want to release something with SDK v4, but in the meantime, can we get something for people who don't want to migrate to latest Auth0/NextJS but just the latest NextJS?

guabu commented 1 week ago

Hi all πŸ‘‹ We've just released the v4 alpha which you could try out here.

I understand that we want to release something with SDK v4, but in the meantime, can we get something for people who don't want to migrate to latest Auth0/NextJS but just the latest NextJS?

We definitely plan to address some of the issues in v3 as we understand that not everyone can migrate right away. We did try to keep breaking changes to a minimum but there are certainly differences and we'll be providing a migration guide soon.

However, Next 15 support will most likely not land in v3 as it will require breaking changes.

steelbrain commented 1 week ago

However, Next 15 support will most likely not land in v3 as it will require breaking changes.

I would like to navigate this together! Based on my understanding (and I could be missing something), the only changes that would apply to Auth0/NextJS would be changing cookies() and headers() etc to have an await in front of them.

Based on my understanding of Javascript, await-ing a non-Promise value is a no-op. So it's pretty safe to add that await and say, run Next.js v14 or v13 etc. So those changes would make v3 compatible across the board here.

Thoughts?

johncarmack1984 commented 5 days ago

While the auth0 team works on an official solution, I was able to get a fork working with Next 15. Anyone who needs a drop-in solution today is welcome to use it.

bun add github:johncarmack1984/nextjs-auth0#bada487ceaace86195af774a5d361465cb3688d3
iansbrash commented 4 days ago

Here's a patch I created while we wait. Works for me on the dev server (am yet to push this to production) on @auth0/nextjs-auth 3.5.0 and nextjs 15.0.2-canary.5. Check out how to apply this patch here. Hope this helps someone.

diff --git a/node_modules/@auth0/nextjs-auth0/dist/auth0-session/session/stateful-session.js b/node_modules/@auth0/nextjs-auth0/dist/auth0-session/session/stateful-session.js
index 452c9bc..c05ca52 100644
--- a/node_modules/@auth0/nextjs-auth0/dist/auth0-session/session/stateful-session.js
+++ b/node_modules/@auth0/nextjs-auth0/dist/auth0-session/session/stateful-session.js
@@ -25,7 +25,7 @@ class StatefulSession extends abstract_session_1.AbstractSession {
     async getSession(req) {
         const config = await this.getConfig(req);
         const { name: sessionName } = config.session;
-        const cookies = req.getCookies();
+        const cookies = await req.getCookies();
         const keys = await this.getKeys(config);
         const sessionId = await (0, signed_cookies_1.getCookieValue)(sessionName, cookies[sessionName], keys);
         if (sessionId) {
@@ -39,7 +39,7 @@ class StatefulSession extends abstract_session_1.AbstractSession {
         const config = await this.getConfig(req);
         const store = await this.getStore(config);
         const { name: sessionName, genId } = config.session;
-        const cookies = req.getCookies();
+        const cookies = await req.getCookies();
         const keys = await this.getKeys(config);
         let sessionId = await (0, signed_cookies_1.getCookieValue)(sessionName, cookies[sessionName], keys);
         // If this is a new session created by a new login we need to remove the old session
@@ -64,7 +64,7 @@ class StatefulSession extends abstract_session_1.AbstractSession {
     async deleteSession(req, res, cookieOptions) {
         const config = await this.getConfig(req);
         const { name: sessionName } = config.session;
-        const cookies = req.getCookies();
+        const cookies = await req.getCookies();
         const keys = await this.getKeys(config);
         const sessionId = await (0, signed_cookies_1.getCookieValue)(sessionName, cookies[sessionName], keys);
         if (sessionId) {
diff --git a/node_modules/@auth0/nextjs-auth0/dist/auth0-session/session/stateless-session.js b/node_modules/@auth0/nextjs-auth0/dist/auth0-session/session/stateless-session.js
index dac0705..3727a8f 100644
--- a/node_modules/@auth0/nextjs-auth0/dist/auth0-session/session/stateless-session.js
+++ b/node_modules/@auth0/nextjs-auth0/dist/auth0-session/session/stateless-session.js
@@ -55,7 +55,7 @@ class StatelessSession extends abstract_session_1.AbstractSession {
     async getSession(req) {
         const config = await this.getConfig(req);
         const { name: sessionName } = config.session;
-        const cookies = req.getCookies();
+        const cookies = await req.getCookies();
         let existingSessionValue;
         if (sessionName in cookies) {
             // get JWE from un-chunked session cookie
@@ -96,7 +96,7 @@ class StatelessSession extends abstract_session_1.AbstractSession {
     async setSession(req, res, session, uat, iat, exp, cookieOptions) {
         const config = await this.getConfig(req);
         const { name: sessionName } = config.session;
-        const cookies = req.getCookies();
+        const cookies = await req.getCookies();
         debug('found session, creating signed session cookie(s) with name %o(.i)', sessionName);
         const [key] = await this.getKeys(config);
         const value = await this.encrypt(session, { iat, uat, exp }, key);
@@ -123,7 +123,7 @@ class StatelessSession extends abstract_session_1.AbstractSession {
     async deleteSession(req, res, cookieOptions) {
         const config = await this.getConfig(req);
         const { name: sessionName } = config.session;
-        const cookies = req.getCookies();
+        const cookies = await req.getCookies();
         for (const cookieName of Object.keys(cookies)) {
             if (cookieName.match(`^${sessionName}(?:\\.\\d)?$`)) {
                 res.clearCookie(cookieName, cookieOptions);
diff --git a/node_modules/@auth0/nextjs-auth0/dist/auth0-session/transient-store.js b/node_modules/@auth0/nextjs-auth0/dist/auth0-session/transient-store.js
index 0cfd094..0931ae7 100644
--- a/node_modules/@auth0/nextjs-auth0/dist/auth0-session/transient-store.js
+++ b/node_modules/@auth0/nextjs-auth0/dist/auth0-session/transient-store.js
@@ -59,7 +59,7 @@ class TransientStore {
      * @return {String|undefined} Cookie value or undefined if cookie was not found.
      */
     async read(key, req, res) {
-        const cookies = req.getCookies();
+        const cookies = await req.getCookies();
         const cookie = cookies[key];
         const config = await this.getConfig(req);
         const cookieConfig = config.transactionCookie;
diff --git a/node_modules/@auth0/nextjs-auth0/dist/handlers/auth.js b/node_modules/@auth0/nextjs-auth0/dist/handlers/auth.js
index 28fa778..8b6e547 100644
--- a/node_modules/@auth0/nextjs-auth0/dist/handlers/auth.js
+++ b/node_modules/@auth0/nextjs-auth0/dist/handlers/auth.js
@@ -38,7 +38,7 @@ exports.default = handlerFactory;
  */
 const appRouteHandlerFactory = (customHandlers, onError) => async (req, ctx) => {
     const { params } = ctx;
-    let route = params.auth0;
+    let route = (await params).auth0;
     if (Array.isArray(route)) {
         let otherRoutes;
         [route, ...otherRoutes] = route;
diff --git a/node_modules/@auth0/nextjs-auth0/dist/http/auth0-next-request-cookies.js b/node_modules/@auth0/nextjs-auth0/dist/http/auth0-next-request-cookies.js
index 3f948e5..9eb445c 100644
--- a/node_modules/@auth0/nextjs-auth0/dist/http/auth0-next-request-cookies.js
+++ b/node_modules/@auth0/nextjs-auth0/dist/http/auth0-next-request-cookies.js
@@ -8,8 +8,11 @@ class Auth0NextRequestCookies extends http_1.Auth0RequestCookies {
     getCookies() {
         // eslint-disable-next-line @typescript-eslint/no-var-requires
         const { cookies } = require('next/headers');
-        const cookieStore = cookies();
-        return cookieStore.getAll().reduce((memo, { name, value }) => (Object.assign(Object.assign({}, memo), { [name]: value })), {});
+        return cookies().then((cookieStore) => {
+            return cookieStore
+                .getAll()
+                .reduce((memo, { name, value }) => (Object.assign(Object.assign({}, memo), { [name]: value })), {});
+        });
     }
 }
 exports.default = Auth0NextRequestCookies;
diff --git a/node_modules/@auth0/nextjs-auth0/dist/http/auth0-next-response-cookies.js b/node_modules/@auth0/nextjs-auth0/dist/http/auth0-next-response-cookies.js
index ea7e5e4..0df54c5 100644
--- a/node_modules/@auth0/nextjs-auth0/dist/http/auth0-next-response-cookies.js
+++ b/node_modules/@auth0/nextjs-auth0/dist/http/auth0-next-response-cookies.js
@@ -17,24 +17,30 @@ class Auth0NextResponseCookies extends http_1.Auth0ResponseCookies {
     setCookie(name, value, options) {
         // eslint-disable-next-line @typescript-eslint/no-var-requires
         const { cookies } = require('next/headers');
-        const cookieSetter = cookies();
-        try {
-            cookieSetter.set(Object.assign(Object.assign({}, options), { name, value }));
-        }
-        catch (_) {
-            warn();
-        }
+         cookies().then((cookieSetter) => {
+            try {
+                cookieSetter.set(
+                    Object.assign(Object.assign({}, options), {
+                        name,
+                        value,
+                    })
+                );
+            } catch (_) {
+                warn();
+            }
+        })
     }
     clearCookie(name, options) {
         // eslint-disable-next-line @typescript-eslint/no-var-requires
         const { cookies } = require('next/headers');
-        const cookieSetter = cookies();
-        try {
-            cookieSetter.set(Object.assign(Object.assign({}, options), { name, value: '', expires: new Date(0) }));
-        }
-        catch (_) {
-            warn();
-        }
+        cookies().then((cookieSetter) => {
+            try {
+                cookieSetter.set(Object.assign(Object.assign({}, options), { name, value: '', expires: new Date(0) }));
+            }
+            catch (_) {
+                warn();
+            }
+        })
     }
 }
 exports.default = Auth0NextResponseCookies;
diff --git a/node_modules/@auth0/nextjs-auth0/src/http/auth0-next-request-cookies.ts b/node_modules/@auth0/nextjs-auth0/src/http/auth0-next-request-cookies.ts
index dd93945..d06856d 100644
--- a/node_modules/@auth0/nextjs-auth0/src/http/auth0-next-request-cookies.ts
+++ b/node_modules/@auth0/nextjs-auth0/src/http/auth0-next-request-cookies.ts
@@ -8,13 +8,15 @@ export default class Auth0NextRequestCookies extends Auth0RequestCookies {
   public getCookies(): Record<string, string> {
     // eslint-disable-next-line @typescript-eslint/no-var-requires
     const { cookies } = require('next/headers');
-    const cookieStore = cookies();
-    return cookieStore.getAll().reduce(
-      (memo: Record<string, string>, { name, value }: { name: string; value: string }) => ({
+    return cookies().then((cookieStore) => {
+      console.log('cookieStore', cookieStore);
+      return cookieStore.getAll().reduce(
+        (memo: Record<string, string>, { name, value }: { name: string; value: string }) => ({
         ...memo,
         [name]: value
       }),
-      {}
-    );
+        {}
+      );
+    });
   }
 }
diff --git a/node_modules/@auth0/nextjs-auth0/src/http/auth0-next-response-cookies.ts b/node_modules/@auth0/nextjs-auth0/src/http/auth0-next-response-cookies.ts
index c690479..a511603 100644
--- a/node_modules/@auth0/nextjs-auth0/src/http/auth0-next-response-cookies.ts
+++ b/node_modules/@auth0/nextjs-auth0/src/http/auth0-next-response-cookies.ts
@@ -22,22 +22,25 @@ export default class Auth0NextResponseCookies extends Auth0ResponseCookies {
   public setCookie(name: string, value: string, options?: CookieSerializeOptions) {
     // eslint-disable-next-line @typescript-eslint/no-var-requires
     const { cookies } = require('next/headers');
-    const cookieSetter = cookies();
-    try {
-      cookieSetter.set({ ...options, name, value });
-    } catch (_) {
-      warn();
-    }
+    cookies().then((cookieSetter) => {
+      try {
+        cookieSetter.set({ ...options, name, value });
+      } catch (_) {
+        warn();
+      }
+    });
   }

   public clearCookie(name: string, options?: CookieSerializeOptions) {
     // eslint-disable-next-line @typescript-eslint/no-var-requires
     const { cookies } = require('next/headers');
-    const cookieSetter = cookies();
-    try {
-      cookieSetter.set({ ...options, name, value: '', expires: new Date(0) });
-    } catch (_) {
-      warn();
-    }
+    cookies().then((cookieSetter) => {
+        try {
+          cookieSetter.set({ ...options, name, value: '', expires: new Date(0) });
+        } catch (_) {
+          warn();
+        }
+    })
+    
   }
 }

@auth0+nextjs-auth0+3.5.0.patch

mikebywaters commented 3 days ago

Thanks @iansbrash

Here's a yarn patch for anyone using yarn >2 who also wants support now and doesn't want to use the v4 alpha

@auth0-nextjs-auth0-npm-3.5.0-2a90316d19.patch

yarn patch docs

aidanhchau commented 1 day ago

any updates / expected dates for when this issue will be resolved? quite significant blocker for us.

guabu commented 1 day ago

Hey all πŸ‘‹ We're currently discussing this and will share an update on whether we'll be adding support for Next 15 in v3 of the SDK or recommending the upgrade to v4.

We should have a more concrete answer this week!