nahuel.luca()

Magic Link with Auth0 and NextJS

In this blog we implement Auth0 with nextjs and use magin link how login method. We use NextJS 14 with app router and nextjs-auth SDK

☝🏾 Magic Links are not compatible with the New Universal Login. Magic link uses the Classic Universal Login, the problem is that the initial request and response works if done in the same browser. So this is a problem for iOS users.

Another clarification:

💡 You are using the Auth0 Email Provider which is only intended for development/trial use. Any customizations made to this email template will NOT take effect until a Custom Email Provider is configured. All emails sent will use the default Auth0 templates, even if customized. Please enable a Custom Email Provider for production use of custom templates.

With this in mind, let's get started.

Install and setting nextjs-auth0 and Auth0

First, we have to configure the Auth0 application as a Regular Web Application, then we configure the callback URLs so that Auth0 redirects our users when they try to login or logout. Go to settings on application in auth0 and configurate http://localhost:3000/api/auth/callback in Allowed Callbacks URLs and http://localhost:3000/ in Allowed Logout URLs.

The we need instal npm install @auth0/nextjs-auth0 and setup envs in .env.local

AUTH0_SECRET='use [openssl rand -hex 32] to generate a 32 bytes value'
AUTH0_BASE_URL='[http://localhost:3000](http://localhost:3000/)'
AUTH0_ISSUER_BASE_URL='https://{yourDomain}'
AUTH0_CLIENT_ID='{yourClientId}'
AUTH0_CLIENT_SECRET='{yourClientSecret}'

Then we will have to configure Auth0 to use magic link, you can follow this documentation.

Settings in NextJS

Create dynamic API route:

// app/api/auth/[auth0]/route.js
import { handleAuth } from '@auth0/nextjs-auth0';

export const GET = handleAuth();

And then configurate UserProvider

// app/layout.jsx
import { UserProvider } from '@auth0/nextjs-auth0/client';

export default function RootLayout({ children }) {
  return (
    <html lang="en">
    <UserProvider>
      <body>{children}</body>
    </UserProvider>
    </html>
  );
}

Create a SessionButton

// components/session-button.tsx
import {getSession} from "@auth0/nextjs-auth0";

import {Button} from "./ui/button";

export default async function SessionButton() {
  const user = await getSession();

  return user ? (
    <Button asChild variant={"destructive"}>
      <a href="/api/auth/logout">Logout</a>
    </Button>
  ) : (
    <Button asChild>
      <a href="/api/auth/login">Login</a>
    </Button>
  );
}

☝ We use the <a> tag because the <Link> component of NextJS is for client-side navigation and we are calling an API route.

And a page for view user Info

// app/me/page.tsx
import {getSession} from "@auth0/nextjs-auth0";
import Image from "next/image";

import {Card, CardContent, CardFooter, CardHeader, CardTitle} from "@/components/ui/card";
import SessionButton from "@/components/session-button";

export default async function Page() {
  const dataSession = await getSession();
  const user = dataSession?.user;

  return (
    <section className="flex gap-5 min-h-screen flex-col items-center p-24">
      {user ? (
        <Card className="max-w-[300px]  grid justify-center items-center">
          <CardHeader>
            <CardTitle>My User</CardTitle>
            <Image
              alt={user.name}
              className="rounded-full"
              height={80}
              src={user.picture}
              width={80}
            />
          </CardHeader>
          <CardContent>
            <h2 className="text-lg font-semibold">Name: {user.nickname}</h2>
            <p>Email: {user.email}</p>
          </CardContent>
          <CardFooter>
            <SessionButton />
          </CardFooter>
        </Card>
      ) : (
        <>
          <h3 className="text-2xl font-semibold">Login for see your user</h3>
          <SessionButton />
        </>
      )}
    </section>
  );
}

Create middleware

We will create a middleware because the expiration is not updated with the user's visits, we solve this withMiddlewareAuthRequired.

// middleware.ts
import {withMiddlewareAuthRequired} from "@auth0/nextjs-auth0/edge";

export default withMiddlewareAuthRequired();

// define the protected routes
export const config = {
  matcher: "/protected-route",
};

The end

Thank you for making it to the end. If you like you can review a sample app. This is the code of the app and this is the deployed version.

Resources