Quickstart · Next.js
Add Authfu to Next.js
Connect a Next.js App Router project with Auth.js v5 or NextAuth v4, then protect a server-rendered page.
Create an Authfu app
Everything on the Authfu side happens in the console at authfu.app. Sign in — there is no password, you get a one-time link — then:
- Go to Applications and choose Create app.
- Give it a name. Punctuation is fine — Acme Notes becomes
acme-notes, and the console shows you the name it will use before you confirm. - You land on the app's Quickstart tab. Your Client ID is shown in full; the Client secret sits behind Reveal and can be rotated later from Credentials.
- Leave the app in Development while you build. Development apps send no real email — sign-in links land in the app's Test inbox, so you can log in without touching a mail provider.
Keep that tab open. It watches for your first sign-in and flips to Connected the moment your app completes the exchange — which is how you know the wiring is right rather than guessing.
Now register the Redirect URI — the address Authfu sends the user back to after they sign in. It must match what your app listens on exactly: scheme, host, port and path. A mismatch is the most common reason an integration fails, and it fails at the redirect with an error rather than silently.
Add this one while developing locally:
text http://localhost:3000/api/auth/callback/authfuAnd add your deployed URL before you ship — both can be registered at once:
text https://yourapp.com/api/auth/callback/authfuDevelopment apps do not send email. Sign in as the app owner, a workspace member, or an explicitly configured test user; the one-time link appears in the app's Test inbox.
Install the packages
Only your authentication framework is needed.
shell npm install next-auth@betaConfigure environment variables
Create
.env.local. Generate one secret withopenssl rand -base64 32, then paste the same output into both session-secret variables so either tab works..env.local dotenvAUTHFU_CLIENT_ID=... # from the Authfu console AUTHFU_CLIENT_SECRET=... # from the Authfu console AUTHFU_ISSUER=https://authfu.app AUTH_URL=http://localhost:3000 NEXTAUTH_URL=http://localhost:3000 AUTH_SECRET=... # paste the output of: openssl rand -base64 32 NEXTAUTH_SECRET=... # paste the same value for NextAuth v4Configure authentication
Create
auth.tsat the project root. The provider reads the Authfu credentials and issuer from the environment.auth.ts typescriptimport NextAuth from "next-auth" export const { handlers, auth, signIn, signOut } = NextAuth({ providers: [ { id: "authfu", name: "Authfu", type: "oidc", issuer: process.env.AUTHFU_ISSUER, clientId: process.env.AUTHFU_CLIENT_ID, clientSecret: process.env.AUTHFU_CLIENT_SECRET, authorization: { params: { scope: "openid profile email" } }, checks: ["pkce", "state"], }, ], })Add the route handler
Create the App Router catch-all route at
app/api/auth/[...nextauth]/route.ts.app/api/auth/[...nextauth]/route.ts typescriptimport { handlers } from "@/auth" export const { GET, POST } = handlersProtect a page and read the session
Replace
app/page.tsx. Both variants read the session on the server and render the signed-in email. The v5 page presents its own sign-in action; v4 redirects to the built-in provider chooser.app/page.tsx tsximport { auth, signIn, signOut } from "@/auth" export default async function Home() { const session = await auth() if (!session) { return ( <main> <h1>Protected page</h1> <form action={async () => { "use server" await signIn("authfu") }}> <button type="submit">Sign in with Authfu</button> </form> </main> ) } return ( <main> <h1>Protected page</h1> <p>Signed in as {session.user?.email}</p> <pre>{JSON.stringify(session, null, 2)}</pre> <form action={async () => { "use server" await signOut() }}> <button type="submit">Sign out</button> </form> </main> ) }Add sign-out
The protected-page sample already calls the server-side
signOutaction. No additional file is needed.Start the app with
npm run dev, openhttp://localhost:3000, and complete the link from the Development Test inbox. After sign-in, the protected page shows the session; Sign out returns it to the signed-out state.
What you get
Authfu's ID token and userinfo response include these claims:
sub— the stable Authfu user identifier. This is the only value safe to key your records on.email,email_verified— the signed-in address.name,given_name,family_name,nickname— display names.preferred_username— the user's handle within your app's namespace. Handles are unique per app (or per organization, if you share one), so @ada in your app is unrelated to @ada anywhere else. A handle can be renamed, so never key on it.picture— an absolute URL to the avatar; a generated monogram if none is set.updated_at— profile last-modified, as a UNIX timestamp in seconds.roles— role names for this app, including any inherited from a group assignment.groups— organization group names. Empty for a personal app.
Troubleshooting
npm cannot find @authfu/next
No such package exists. Earlier drafts of these docs referenced one; it was removed because it saved about nine lines of configuration and added a dependency for no benefit. Use the provider configuration above.
No link appears for a Development app
The address is not eligible. Development apps only accept the owner, workspace members, and configured test users. In the verified run, adding the throwaway address as an app test user and requesting a new link populated the Test inbox.
Sign-in works for me but a teammate is refused
A new application admits only the person who created it. Nothing grants anybody else access until you assign them or set an access policy naming who may sign in — absence of configuration is not permission. Assign them with authfu apps assign <client-id> --user them@example.com, or ask authfu access explain them@example.com for the exact reason. See Who can sign in.