Skip to content

feat: support Route Handlers#6777

Merged
balazsorban44 merged 17 commits intov4from
feat/route-handlers
Apr 9, 2023
Merged

feat: support Route Handlers#6777
balazsorban44 merged 17 commits intov4from
feat/route-handlers

Conversation

@balazsorban44
Copy link
Member

@balazsorban44 balazsorban44 commented Feb 22, 2023

Adding support for Next.js Route Handlers https://beta.nextjs.org/docs/routing/route-handlers

Given that this is a significant Next.js feature and that @auth/nextjs is not yet worked on, I decided to backport this to next-auth, even if our main focus is the new Auth.js ecosystem.

We do not want to stop shipping cool features like this to our current users!

Before merging, we need to test this a bit more (Created an experimental release, if anyone reading wants to test it #6777 (comment)), and add documentation.

I plan to add it under https://next-auth.js.org/configuration/initialization

Closes #6792

@vercel
Copy link

vercel bot commented Feb 22, 2023

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
next-auth-docs ❌ Failed (Inspect) Apr 9, 2023 9:51am
2 Ignored Deployments
Name Status Preview Comments Updated (UTC)
auth-docs ⬜️ Ignored (Inspect) Apr 9, 2023 9:51am
auth-docs-nextra ⬜️ Ignored (Inspect) Apr 9, 2023 9:51am

const cookieHeader = serialize(name, value, options)
if (headers.has("Set-Cookie")) headers.append("Set-Cookie", cookieHeader)
else headers.set("Set-Cookie", cookieHeader)
// headers.set("Set-Cookie", cookieHeader) // TODO: Remove. Seems to be a bug with Headers in the runtime
Copy link
Member Author

@balazsorban44 balazsorban44 Feb 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to verify this in Next.js

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

after upgrading to 13.3.0, I don't need this line anymore 👀

return await NextAuthHandler(req as any, res as any, args[0])

// REVIEW: req instanceof Request should return true on Route Handlers
// if (req instanceof Request) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to verify this in Next.js

@balazsorban44 balazsorban44 added legacy Refers to `next-auth` v4. Minimal maintenance. and removed core Refers to `@auth/core` labels Feb 22, 2023
@balazsorban44 balazsorban44 temporarily deployed to Preview February 22, 2023 01:18 — with GitHub Actions Inactive
@github-actions
Copy link

github-actions bot commented Feb 22, 2023

🎉 Experimental release published 📦️ on npm!

@benderillo
Copy link

I am not sure if this is the right place for my comments but I tried this PR in my project and got the following error when trying to sign in with Google. (Note, this was working fine with the api route in the pages folder. I just moved my authOptions to the Route Handler based on this PR example and did yarn add [email protected]

Here is the error:

Access to fetch at 'https://accounts.google.com/o/oauth2/v2/auth?client_id=XXXXXXXXTOP_SECRETXXXXX.apps.googleusercontent.com&scope=openid%20email%20profile&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fcallback%2Fgoogle&state=Zg4KT3w3iT0CEv_0aGwg2mBARmYJb2r2HdDiHuG3tMQ&code_challenge=c8HY4EPC3qxasZDYQtVvaTYieyvR8TvHmyrKF4bSm7g&code_challenge_method=S256' 
(redirected from 'http://localhost:3000/api/auth/signin/google?') from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled

GET https://accounts.google.com/o/oauth2/v2/auth?client_id=XXXXXXXXTOP_SECRETXXXXX.apps.googleusercontent.com&scope=openid%20email%20profile&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fcallback%2Fgoogle&state=Zg4KT3w3iT0CEv_0aGwg2mBARmYJb2r2HdDiHuG3tMQ&code_challenge=c8HY4EPC3qxasZDYQtVvaTYieyvR8TvHmyrKF4bSm7g&code_challenge_method=S256
 net::ERR_FAILED 200


Uncaught (in promise) TypeError: Failed to fetch
    at _callee5$ (webpack-internal:///(:3000/app-client)/./node_modules/next-auth/react/index.js:315:37)
    at tryCatch (webpack-internal:///(:3000/app-client)/./node_modules/@babel/runtime/helpers/regeneratorRuntime.js:44:17)
    at Generator.eval (webpack-internal:///(:3000/app-client)/./node_modules/@babel/runtime/helpers/regeneratorRuntime.js:125:22)
    at Generator.eval [as next] (webpack-internal:///(:3000/app-client)/./node_modules/@babel/runtime/helpers/regeneratorRuntime.js:69:21)
    at asyncGeneratorStep (webpack-internal:///(:3000/app-client)/./node_modules/@babel/runtime/helpers/asyncToGenerator.js:3:24)
    at _next (webpack-internal:///(:3000/app-client)/./node_modules/@babel/runtime/helpers/asyncToGenerator.js:22:9)

Here is how my app/api/auth/[...nextauth]/route.ts looks like:

import NextAuth, { type NextAuthOptions } from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';

import NextAuth, { type NextAuthOptions } from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';
import { addUser } from '@/api/user';

export const authOptions: NextAuthOptions = {
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_OAUTH_ID || '',
      clientSecret: process.env.GOOGLE_OAUTH_SECRET || '',
    }),
  ],
  callbacks: {
    async signIn({ user }) {
      addUser(user);
      return true;
    },
    async session({ session, token }) {
      const user = session?.user;
      if (user) {
        user.id = token.sub;
        user.username = user.email.split('@')[0];
      }
      return session;
    },
  },
  pages: {
    signIn: '/auth/signin',
  },
  secret: process.env.JWT_SECRET,
};

const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };

When I had it in api route, I had no issues with CORS.

@github-actions github-actions bot added the core Refers to `@auth/core` label Feb 24, 2023
@balazsorban44
Copy link
Member Author

balazsorban44 commented Feb 24, 2023

Interesting @benderillo, I'll check this out! Thanks for reporting! #6792 is a great place to report for the future, but I don't mind here either.

UPDATE:

447425d fixed the CORS issue as well! Check #6777 (comment) again.

@ari-becker
Copy link

Update, we are fixing the upstream Next.js issue: vercel/next.js#47718

Seems to be included in next v13.3.0, which was just released

const cookieHeader = serialize(name, value, options)
if (headers.has("Set-Cookie")) headers.append("Set-Cookie", cookieHeader)
else headers.set("Set-Cookie", cookieHeader)
// headers.set("Set-Cookie", cookieHeader) // TODO: Remove. Seems to be a bug with Headers in the runtime
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

after upgrading to 13.3.0, I don't need this line anymore 👀

@balazsorban44
Copy link
Member Author

Thank you for everyone's patience, this is now available in 4.22.0! 🎉 Check out the docs: https://next-auth.js.org/configuration/initialization#route-handlers-app

Remember, this is opt-in, pages will be still supported!

@enyelsequeira
Copy link

Thank you for everyone's patience, this is now available in 4.22.0! 🎉 Check out the docs: https://next-auth.js.org/configuration/initialization#route-handlers-app

Remember, this is opt-in, pages will be still supported!

I tried, installing this, and then testing it out, but unfortunately when I don't log in, hit cancel I get this error Error: This action with HTTP GET is not supported by NextAuth.js. my URL becomes this http://localhost:3000/api/auth/callback?error=access_denied&error_description=The+user+has+denied+your+application+access.&error_uri=https%3A%2F%2Fdocs.github.com%2Fapps%2Fmanaging-oauth-apps%2Ftroubleshooting-authorization-request-errors%2F%23access-denied&state=bGLbaZpEQ7svcm63WdvdCXqS4v3fWqNFOLuOT-YTmTE. does the url of the auth callback have to change?
image
image

@juliusmarminge
Copy link
Contributor

You're missing the provider at the end @enyelsequeira . It should be

http://localhost:3000/api/auth/callback/github

@enyelsequeira
Copy link

/api/auth/callback/github

Oh, wow silly mistake! @juliusmarminge Thank you!

@bencehusi
Copy link

I get "MISSING_NEXTAUTH_API_ROUTE_ERROR" with 4.22.1.
I'm trying to [GET] http://localhost:3000/api/auth/providers which result in this server error.

@GuasaPlay
Copy link

🎉 Experimental release published 📦️ on npm!

@bencehusi I installed the experimental version and it works without problems

@MeowcaTheoRange
Copy link

MeowcaTheoRange commented May 29, 2023

Does anyone have any idea how to use the SessionProvider in App Router? Do I just put it into the function arguments as usual, or is there something else to it?

import { SessionProvider } from "next-auth/react";
import "@/styles/globals.css";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <SessionProvider session={session}>
          <p>Hello, world!</p>
          {children}
        </SessionProvider>
      </body>
    </html>
  );
}

Also, https://authjs.dev/reference/react is gone, so that won't help me.
image

EDIT: Turns out it's still at https://next-auth.js.org/getting-started/client, but it's still for Pages Router.

@MeowcaTheoRange
Copy link

UPDATE: I fixed my issue, turns out SessionProvider does Not need a session variable for App Router.

layout.tsx:

import AuthContext from "@/components/AuthContext";
import "@/styles/globals.css";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <AuthContext>
          <p>Hello, world!</p>
          {children}
        </AuthContext>
      </body>
    </html>
  );
}

components/AuthContext.tsx

"use client";

import { SessionProvider } from "next-auth/react";

export interface AuthContextProps {
  children: React.ReactNode;
}

export default function AuthContext({ children }: AuthContextProps) {
  return <SessionProvider>{children}</SessionProvider>;
}

See this comment for more information.

@Thinkscape
Copy link

UPDATE: I fixed my issue, turns out SessionProvider does Not need a session variable for App Router.

The way your components are written, all of them are client components ("use client") which means that you're not using the App Router (SSR). This is equivalent to plain browser React.

@MeowcaTheoRange
Copy link

UPDATE: I fixed my issue, turns out SessionProvider does Not need a session variable for App Router.

The way your components are written, all of them are client components ("use client") which means that you're not using the App Router (SSR). This is equivalent to plain browser React.

Nesting Server Components inside Client Components?

@Thinkscape
Copy link

In your example above both layout and AuthContext are client, so no nesting occurs. The vanila SessionProvider is a client component that will fetch session from /api/auth/session endpoint. The whole point of using next-auth with App Routers is to prevent additional requests and have a hydrated session as soon as the html comes from the server (from RSC/SSR)

@MeowcaTheoRange
Copy link

Oh. I didn't use 'use client' on layout.tsx, so... how do I... not client it?

@Thinkscape
Copy link

Thinkscape commented May 31, 2023

You wanna keep your layouts RSC (as per docs recommendation).
For performance (but Balazs might frown as there are drawbacks :-) you could fetch the session there and then inject it into a wrapper, i.e.

export default async function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const session = await getServerSession();

  return (
    <SessionProvider session={session}>
      {children}
    </SessionProvider>
  );
}
"use client";

import { SessionProvider as NextAuthSessionProvider } from "next-auth/react";
import { type Session } from "next-auth";
import React from "react";

export default function SessionProvider({
  children,
  session,
}: {
  children: React.ReactNode;
  session?: Session;
}) {
  return (
    <NextAuthSessionProvider
      session={session}
      refetchInterval={500}
      refetchOnWindowFocus
    >
      {children}
    </NextAuthSessionProvider>
  );
}

Benefit is that you'll have a hydrated page with session available on first render/mount (no need for suspense/loading indicators, can even do auth checks).
Key drawback here is that default getServerSession() will strip expiration date, which will make the <NextAuthSessionProvider> reload the session in FE when it mounts. You can work around it by re-injecting the expiration date (and then no browser /api calls will be made), but you're risking having stale session (and i.e. your API calls failing, would need to work around that)

That's how I'm doing that currently, while awaiting some next-auth changes, prob. leveraging next 13.4 features.

@dstroot
Copy link

dstroot commented Jun 13, 2023

@Thinkscape 👍🏼 But this throws a Typescript error in the Layout:

Type 'Session | null' is not assignable to type 'Session | undefined'.

@samtgarson
Copy link

@dstroot you can change session?: Session to session?: Session | null in the props definition of the Provider component to avoid that.

@Arc-a-it
Copy link

@balazsorban44 I ran a CLI experiment on a large JS/TS repo.
A small, reasonable change was BLOCKED due to architectural blast radius.
The decision was arguable, but it forced the question.

I wrote up the exact collision here :
👉 [link to your arcvision-collision-proof repo]

Curious if this would be unacceptable or useful in your world.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core Refers to `@auth/core` enhancement New feature or request legacy Refers to `next-auth` v4. Minimal maintenance.

Projects

None yet

Development

Successfully merging this pull request may close these issues.