Next.js and Auth.js

Working sign-in in three files. Verified against Next.js 16 and Auth.js 5.

1. Start with an organization

Applications belong to an organization, not to you personally. If you do not have one yet: Console → Organization → "Create an organization". Type a name and confirm — Applications shows up in the menu right away.

Already have an organization? Skip straight to the next step.

2. Register a client

Console → Applications → "Add an application". Give it a name — users see it on the consent screen — and a redirect URI:

https://your-site/api/auth/callback/authmy

The same form has a URI builder: type your site address, pick Auth.js, and the path is filled in; a checkbox adds a localhost URI for development.

You then get a client_id and a client_secret. The secret is shown once.

3. Install the library

npm install next-auth

4. Three files

// auth.ts
import NextAuth from 'next-auth'

export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [
    {
      id: 'authmy',
      name: 'auth.my',
      type: 'oidc',
      issuer: 'https://auth.my',
      clientId: process.env.AUTHMY_CLIENT_ID,
      clientSecret: process.env.AUTHMY_CLIENT_SECRET,
    },
  ],
})
// app/api/auth/[...nextauth]/route.ts
import { handlers } from '@/auth'

export const { GET, POST } = handlers
// app/page.tsx
import { auth, signIn } from '@/auth'

export default async function Home() {
  const session = await auth()

  if (session?.user) return <p>Signed in as {session.user.email}</p>

  return (
    <form action={async () => { 'use server'; await signIn('authmy') }}>
      <button>Sign in with auth.my</button>
    </form>
  )
}
Careful

Handlers come out of a handlers object. Writing export { GET, POST } from '@/auth' does not work — the site returns 500 on the very first request.

5. Environment

AUTHMY_CLIENT_ID=…
AUTHMY_CLIENT_SECRET=…
AUTH_SECRET=…

AUTH_SECRET is your own secret; Auth.js signs its own session cookie with it. It has nothing to do with auth.my. Generate one:

openssl rand -base64 32

Shorter: the wrapper package

The same defaults are gathered in a package, @authmy/authjs. It is a shortcut, not the way in: auth.my is a plain OpenID Connect provider, and the fifteen lines above do exactly the same thing.

npm install @authmy/authjs
// auth.ts
import NextAuth from 'next-auth'
import AuthMy from '@authmy/authjs'

export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [AuthMy()],
})

clientId and clientSecret are read from AUTHMY_CLIENT_ID and AUTHMY_CLIENT_SECRET — the same variables as above.

What it pins down: the issuer, the scope openid profile email, PKCE, and the provider id authmy, which the redirect URI is built from. It adds nothing to the protocol and will not become required: a free choice of library is a promise made on the front page.

What you do not configure

No signing algorithm, no endpoint list, no PKCE setup, no refresh handling. The library reads all of it from discovery and does it itself.

Who signed in

const session = await auth()
session?.user?.email

The permanent identifier is sub, not the email. To carry it into the session:

callbacks: {
  jwt({ token, profile }) {
    if (profile?.sub) token.sub = profile.sub
    return token
  },
  session({ session, token }) {
    if (session.user) session.user.id = token.sub as string
    return session
  },
}

The whole thing

A ready-to-run example lives in the repository: examples/nextjs-authjs.

Did this page help?