Authfu

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.

  1. 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:

    1. Go to Applications and choose Create app.
    2. 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.
    3. 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.
    4. 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/authfu

    And add your deployed URL before you ship — both can be registered at once:

    text
    https://yourapp.com/api/auth/callback/authfu

    Development 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.

  2. Install the packages

    Only your authentication framework is needed.

    shell
    npm install next-auth@beta
  3. Configure environment variables

    Create .env.local. Generate one secret with openssl rand -base64 32, then paste the same output into both session-secret variables so either tab works.

    .env.local
    AUTHFU_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 v4
  4. Configure authentication

    Create auth.ts at the project root. The provider reads the Authfu credentials and issuer from the environment.

    auth.ts
    import 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"],
        },
      ],
    })
  5. Add the route handler

    Create the App Router catch-all route at app/api/auth/[...nextauth]/route.ts.

    app/api/auth/[...nextauth]/route.ts
    import { handlers } from "@/auth"
    
    export const { GET, POST } = handlers
  6. Protect 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
    import { 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>
      )
    }
  7. Add sign-out

    The protected-page sample already calls the server-side signOut action. No additional file is needed.

    Start the app with npm run dev, open http://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.

Next steps