mickasmt / next-saas-stripe-starter

Open-source SaaS Starter with User Roles & Admin Panel. Built using Next.js 14, Prisma, Neon, Auth.js v5, Resend, React Email, Shadcn/ui, Stripe, Server Actions.
https://next-saas-stripe-starter.vercel.app
MIT License
1.94k stars 351 forks source link

How to sign up/sign in using Auth.js Magic Link #25

Closed marftony closed 6 months ago

marftony commented 7 months ago

Hi, first of all thanks for this open source saas starter. It's saving me hundreds of hours of development. I've successfully set up Google Authentication flow. Right now I am struggling implementing the magic link sign in/sign up. From what I understand, the main flow is in auth.config.ts. You've commented out the supposed code required to implement it. I've tried uncommenting it, but it throws error cannot find Email() . I've read the new Auth.JS 5.0 guides considering that you just upgraded it, and, from what I understand, it should be something like this:

providers: [
Resend({
      from: "auth@app.company.com",
    }),
]

I've tried replacing Email() with Resend() but still won't work. No project docs for this topic to look up either. My question is, have you successfully found a solution for this? I'm stuck on this since 2 days already. If you could give me a hand on this, I'd really appreciate it. Thank in advance

tomgreeEn commented 7 months ago

It's an issue with the middleware, which cannot run Prisma client at the edge. Currently the implementation is using the "old edge" workaround below, which does not work for Email as it needs to connect to the DB.

https://authjs.dev/getting-started/adapters/prisma

It does look like it's possible to use the Neon serverless driver to allow DB access from middleware, though not sure if @mickasmt is planning to implement this?

I'm using Railway instead of Neon so am rolling back to authjs V4 for now as it does not like the two are compatible currently.

tomgreeEn commented 7 months ago

I solved this by extending the "old edge" work around. It's not that nice but does seem to work so far.

Do not include the Resend provider in auth.config.js, as this is called in Middleware and expects a DB connection. The auth.ts file is the "full" auth config which can include files which can't run at the edge.

Move the Resend provider to auth.ts config, so that it looks like:

export const {
  handlers: { GET, POST },
  auth,
} = NextAuth({
  adapter: PrismaAdapter(prisma),
  session: { strategy: "jwt" },
  pages: {
    signIn: paths.login,
    // error: "/auth/error",
  },
  callbacks: {
    async session({ token, session }) {
    // {...your callbacks ...}
    },
    },
  ...authConfig,
  providers: [
    ...authConfig.providers,
    Resend({
// {...your Resend config}
)}]
// debug: process.env.NODE_ENV !== "production"
});

I believe this is OK because the middleware only needs to check from the jwt token that the current session is valid, and does not need access to any of the specific Resend methods, or to make a DB call. But interested if other folks this is a safe approach. Hopefully as v5 evolves a better solution will come up

marftony commented 7 months ago

Hi Tom, thanks for reaching out and trying to help. I've tried your solution but it still didn't work for me. I just managed to get it working by including the resend provider inside auth.config.ts, and replacing "email" with "resend" inside user-auth-form.tsx, and specifically here:

const signInResult = await signIn("resend", {
      email: data.email.toLowerCase(),
      redirect: false,
      callbackUrl: searchParams?.get("from") || "/dashboard",
    })

It is now sending both activation and confirmation emails, however i'm facing an issue. It seems that whenever I sign up using the activation token, an Account query is not being created in NeonDB, but just a Userquery. Infact, after i click on the login button inside the verification email, it redirects me again to the login page, using this URL:

http://localhost:3000/login?callbackUrl=%2Fdashboard

If I head to the landing page, there's a button with the avatar of the logged in user, as usual. If i click on it, and try to access either Dashboard, Billing, or Settings, it redirects me to the Login page with either one of these URLs:

http://localhost:3000/login?callbackUrl=%2Fdashboard http://localhost:3000/login?callbackUrl=%2Fbilling http://localhost:3000/login?callbackUrl=%2Fsettings

It seems that, as you mentioned, the issue lies in middleware.ts. I've specifically had a look at this code snippet, however I'm not really able to understand the core issue:

if (!isLoggedIn && !isPublicRoute) {
    let callbackUrl = nextUrl.pathname;
    if (nextUrl.search) {
      callbackUrl += nextUrl.search;
    }

    const encodedCallbackUrl = encodeURIComponent(callbackUrl);

    return Response.redirect(new URL(
      `/login?callbackUrl=${encodedCallbackUrl}`,
      nextUrl
    ));
  }

Is there any workaround we could try to fix this or should we just give up?

tomgreeEn commented 7 months ago

I can't really help with your specific issues as my project has diverged a lot from this template but one thing to say is that magic sign in won't / might not work in local production (or at least, it doesn't for me, and if it is possible you'll need to go through some extra steps to make it happen). So worth trying on an live build - this works for me.

marftony commented 7 months ago

Ok, I found a temporary fix that seems to make the magic link flow work flawlessly.

const signInResult = await signIn("resend", {
      email: data.email.toLowerCase(),
      redirect: false,
      callbackUrl: searchParams?.get("from") || "/dashboard",
    })

inside user-auth-form.tsx ;

then edited the code inside auth.ts as per your suggestion.

Last, I left auth.config.ts untouched.

This works fine in development, pushing now to production and I'll let you know whether it works there too or not.

Thanks for the help @tomgreeEn

marftony commented 7 months ago

An update @tomgreeEn

Google Authentication and email auth both work perfectly in development, however in production:

1) Upon trying to sign in with Google, it displays a generic error screen "Error" with this URL path:

https://www.mysite.com/api/auth/error?error=AdapterError

While the email login displays a successful toast, yet the email is not delivered.

Has this ever happened to you by any chance? I haven't found any similar issue on google experienced by anyone.

Upon debugging in dev mode, I found that, when trying to sign in, it calls a getUserByAccount() method:

adapter_getUserByAccount {
  "args": [
    {
      "providerAccountId": "123456789123456789",
      "provider": "google"
    }
  ]
}

Thanks in advance

mickasmt commented 6 months ago

Hey @marftony @tomgreeEn sorry for the late. Im working on this part too but i got some errors with the middleware. There are few open issues on the authjs repo and it seems that middleware not work well.

@/lib/db.ts i use with the adapter:

import { Pool } from "@neondatabase/serverless";
import { PrismaNeon } from "@prisma/adapter-neon";
import { PrismaClient } from "@prisma/client";
import "server-only";

import { env } from "@/env.mjs";

const prismaClientSingleton = () => {
  const neon = new Pool({ connectionString: env.DATABASE_URL });
  const adapter = new PrismaNeon(neon);
  return new PrismaClient({ adapter });
};

declare global {
  var prismaGlobal: undefined | ReturnType<typeof prismaClientSingleton>;
}

export const prisma = globalThis.prismaGlobal ?? prismaClientSingleton();

if (process.env.NODE_ENV !== "production") globalThis.prismaGlobal = prisma;

I found a recent video that talks about this topic. Check it out here at 24:23

marftony commented 6 months ago

@mickasmt Hi, thank you for replying.

I will try installing neon serverless drivers and entering your code and let you know.

Just wondering, are you receiving errors just in production or also locally? As for me it works perfectly locally.

Thanks in advance

mickasmt commented 6 months ago

@marftony On local, it works for me too, but I receive warnings for each request.

 ⚠ ./node_modules/.pnpm/@prisma+client@5.13.0_prisma@5.13.0/node_modules/.prisma/client/wasm-edge-light-loader.js
The generated code contains 'async/await' because this module is using "topLevelAwait".
However, your target environment does not appear to support 'async/await'.
As a result, the code may not run as expected or may cause runtime errors.

If I disable the middleware by renaming the file _middleware.ts, the warnings disappear. There are a few open issues on Prisma regarding this. So, I think I'll keep an eye on the next updates from authjs and Prisma to see if they address this. I don't want to break the app by adding this feature.

marftony commented 6 months ago

Hi @mickasmt, thanks for reverting back. After 2 days of debugging, I eventually found out that for me it didn't work in production because of a mismatch typo in Vercel DATABASE_URL environment variable string 🤦🏻 now it works like a charm. I'm now working on newsletter audience subscriptions, I'll keep an eye on this repo when this task is implemented! Thanks again for your help and for this amazing saas template. I really love it.