Authentication for Next.js.
isc-auth is a flexible authentication library for Next.js applications. It supports both the Pages Router and the App Router, a wide range of OAuth providers, email magic links, and credential-based sign in. Sessions can be stored in a database or as stateless JSON Web Tokens (JWTs).
- Next.js first-class support — works with the Pages Router (
pages/api/auth) and the App Router (app/api/auth/[...iscauth]/route.js). - Many providers out of the box — GitHub, Google, Twitter, Facebook, GitLab, Discord, Apple, and many more, plus
Credentials,Email, andPhoneproviders. - Email + password registration — built-in sign-up API endpoint (
signUpEmail). - Phone number sign in — SMS one-time-code sign up / sign in via the
Phoneprovider. - Password management — change password, request password reset emails, and reset passwords.
- Flexible sessions — use database-backed sessions or encrypted/signed JWT sessions (default when no database is configured).
- Pluggable databases — TypeORM, Prisma, Fauna, and Drizzle adapters (MySQL, PostgreSQL, MongoDB, MSSQL, SQLite, etc.).
- Customizable — override the built-in pages, and hook into
callbacksandeventsfor full control. - Better Auth compatibility — opt-in response shapes, a programmatic
auth.api, and matching session cookie names make migrating from Better Auth straightforward.
npm install isc-authisc-auth lists react and react-dom as peer dependencies, so make sure they are installed in your project.
Create a catch-all API route and initialize isc-auth with your providers.
Pages Router — pages/api/auth/[...iscauth].js:
import ISCAuth from "isc-auth"
import Providers from "isc-auth/providers"
export default ISCAuth({
providers: [
Providers.GitHub({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
],
session: {
jwt: true,
},
jwt: {
secret: process.env.SECRET,
},
})App Router — app/api/auth/[...iscauth]/route.js:
import ISCAuthApp from "isc-auth/app"
import Providers from "isc-auth/providers"
const handler = ISCAuthApp({
providers: [
Providers.GitHub({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
],
})
export const GET = handler
export const POST = handlerCopy .env.example to .env.local and fill in your provider credentials, plus a long random SECRET and the public URL of your app:
ISCAUTH_URL=http://localhost:3000
GITHUB_ID=
GITHUB_SECRET=
SECRET=The
ISCAUTH_URLenvironment variable must be set for OAuth redirects to work correctly in production.
In pages/_app.js, use the Provider so that useSession() works anywhere in your client components:
import { Provider } from "isc-auth/client"
export default function App({ Component, pageProps }) {
return (
<Provider session={pageProps.session}>
<Component {...pageProps} />
</Provider>
)
}On the client:
import { useSession, signIn, signOut } from "isc-auth/client"
const [session, loading] = useSession()On the server / in an API route:
import { getSession } from "isc-auth/client"
const session = await getSession({ req })isc-auth accepts a single options object. The most common options are:
| Option | Description |
|---|---|
providers |
Array of configured providers (see isc-auth/providers). |
database |
A database connection URI or config object (enables DB-backed sessions). |
adapter |
An adapter instance for advanced database usage. |
session |
Session options (jwt, maxAge, updateAge). |
jwt |
JWT options (secret, encryption, custom encode/decode). |
signup |
Email + password registration options (minPasswordLength, autoSignIn). |
otp |
Phone OTP options (rateLimits, custom in-memory store). |
forgotPassword |
Password reset delivery: sendResetRequest({ identifier, url, token, provider }), plus rate limit and token lifetime settings. |
compat |
Compatibility switches, e.g. compat: { betterAuth: true }. |
pages |
Override the built-in sign-in, sign-out, error, and verify-request pages. |
callbacks |
signIn, redirect, session, and jwt callbacks. |
events |
signIn, signOut, createUser, etc. for logging/side effects. |
debug |
Enable verbose console logging. |
isc-auth/providers exports Credentials, Email, Phone, and a large set of OAuth providers including: Apple, Atlassian, Auth0, Azure AD B2C, Basecamp, Battle.net, Box, Bungie, Cognito, Discord, Facebook, Foursquare, FusionAuth, GitHub, GitLab, Google, IdentityServer4, LinkedIn, MailRu, Mixer, Netlify, Okta, Reddit, Slack, Spotify, Strava, Twitch, Twitter, VK, and Yandex.
With an adapter configured, users can register with a name, email, and password (hashed with scrypt). The endpoint requires no adapter-specific setup:
import { signUpEmail } from "isc-auth/client"
const { user, error } = await signUpEmail({
name: "Ada Lovelace",
email: "ada@example.com",
password: "correct-horse-battery-staple",
})Configure policy via the signup option:
ISCAuth({
providers,
signup: {
minPasswordLength: 10, // default 8
autoSignIn: true, // default true - sets the session cookie on success
},
})Errors are returned as structured codes instead of throwing: USER_EXISTS, WEAK_PASSWORD, INVALID_EMAIL.
Add the Phone provider to enable one-time-code sign up / sign in over SMS:
import Providers from "isc-auth/providers"
ISCAuth({
providers: [
Providers.Phone({
async sendVerificationRequest({ identifier: phone, token, provider }) {
await mySmsClient.send(phone, `Your code is ${token}`)
},
}),
],
otp: {
rateLimits: {
sendPerPhonePerHour: 3, // default
resendCooldownSeconds: 60, // default
verifyAttempts: 5, // default
},
},
})On the client:
import { sendPhoneOtp, verifyPhoneOtp } from "isc-auth/client"
await sendPhoneOtp({ phone: "+15551234567" }) // triggers sendVerificationRequest
const { user, error } = await verifyPhoneOtp({ phone: "+15551234567", code: "123456" })The first verification creates the user account automatically; subsequent verifications sign the existing user in.
Signed-in users can change their password, and password resets work over email-style tokens:
import { changePassword, forgotPassword, resetPassword } from "isc-auth/client"
// signed-in user changes their own password
const { ok, error } = await changePassword({ currentPassword: "...", newPassword: "..." })
// request a reset link/token (always resolves ok - never reveals if the account exists)
await forgotPassword({ email: "ada@example.com" })
// consume the token from the email
const result = await resetPassword({ email: "ada@example.com", token, password: "new-password" })Delivery of reset tokens is your responsibility, via the forgotPassword.sendResetRequest callback:
ISCAuth({
providers,
forgotPassword: {
async sendResetRequest({ identifier: email, url, token, provider }) {
await myEmailClient.send(email, `Reset your password: ${url}`)
},
tokenMaxAgeSeconds: 3600,
},
})Reset tokens are single use, stored hashed, and expire after tokenMaxAgeSeconds (default 1 hour).
For codebases migrating from Better Auth, isc-auth supports drop-in style conventions:
-
Session response shape — set
compat: { betterAuth: true }and/api/auth/sessionreturns{ user, session: { expires } }instead of the flat v3 shape{ user, expires }. Signed-out requests returnnull. -
Programmatic API — the configured handler exposes
auth.api.getSession({ headers })for server-side lookups (accepts WebHeadersinstances or plain objects):// pages/api/auth/[...iscauth].js import ISCAuth from "isc-auth" export const auth = ISCAuth({ ...options }) export default auth // anywhere on the server: const session = await auth.api.getSession({ headers }) // null when signed out
-
Cookie names — import canonical names instead of hardcoding strings:
import { getSessionCookieNames, BETTER_AUTH_COOKIE_ALIASES } from "isc-auth/server"
The classic tuple hook is deprecated:
// before (deprecated)
const [session, loading] = useSession()
// after
const { data: session, isPending: loading, error, refetch } = useAuthSession()Run the bundled codemod to update your source files in place (variable names are preserved as aliases):
node node_modules/isc-auth/tools/codemod-use-session.js src/The tuple form keeps working today and will be removed in 2.0.
npm run build # build JS + CSS into dist/
npm run watch # rebuild on change
npm test # unit + integration tests (uses Docker)The example/ directory contains a working Next.js demo app that exercises isc-auth.
Adapters are available for TypeORM (default), Prisma, Fauna, and Drizzle. Database support includes MySQL, MariaDB, PostgreSQL, MongoDB, MSSQL, and SQLite (depending on the chosen adapter). The Email provider requires a database; OAuth and Credentials providers do not.
Using Drizzle:
import Adapters from "isc-auth/adapters"
import * as schema from "./drizzle-schema" // see dist/adapters/drizzle/schema.js
ISCAuth({
adapter: Adapters.Drizzle({ db, tables: { users: schema.users, ... } }),
})Users created by the new features use three optional columns: passwordHash, phone, and phoneVerified. Add them to your existing schema to enable email + password registration and phone sign in.
ISC