Skip to content

Auth Plugin

Self-contained authentication for Nevr. Supports email/password, OAuth providers, session management, and account linking.

Why Use Auth Plugin?

ChallengeWithout NevrWith Nevr Auth
Password hashingImplement bcrypt/argon2Automatic PBKDF2
Session tokensBuild token systemSecure sessions built-in
OAuth flowPKCE, state, callbacksOne config object
Account securityBuild from scratchFresh sessions, rate limits

Quick Start

typescript
import { nevr } from "nevr"
import { auth } from "nevr/plugins/auth"

const api = nevr({
  entities: [post],
  driver: prisma(db),
  plugins: [
    auth({
      secret: process.env.AUTH_SECRET,  // Required
      baseURL: "http://localhost:3000",
      emailAndPassword: { enabled: true },
    }),
  ],
})

Configuration Reference

Core Options

OptionTypeDefaultDescription
secretstringAUTH_SECRET envRequired. Encryption key for tokens
baseURLstring-Base URL for OAuth callbacks

Email & Password

typescript
auth({
  emailAndPassword: {
    enabled: true,
    minPasswordLength: 8,
    maxPasswordLength: 128,
    autoSignIn: true,
    requireEmailVerification: false,
    disableSignUp: false,
    resetPasswordTokenExpiresIn: 3600,
    revokeSessionsOnPasswordReset: false,
    sendResetPassword: async ({ user, url, token }) => {
      await sendEmail(user.email, `Reset: ${url}`)
    },
    onPasswordReset: async ({ user }) => {
      console.log(`${user.email} reset password`)
    },
  },
})
OptionTypeDefaultDescription
enabledbooleantrueEnable email/password auth
minPasswordLengthnumber8Minimum password characters
maxPasswordLengthnumber128Maximum password characters
autoSignInbooleantrueAuto-signin after signup
requireEmailVerificationbooleanfalseBlock signin until verified
disableSignUpbooleanfalseDisable new registrations
resetPasswordTokenExpiresInnumber3600Reset token TTL (seconds)
revokeSessionsOnPasswordResetbooleanfalseLogout all on password change
sendResetPasswordfunction-Custom reset email sender
onPasswordResetfunction-Post-reset callback

Session Options

typescript
auth({
  session: {
    expiresIn: 60 * 60 * 24 * 7,  // 7 days
    updateAge: 60 * 60 * 24,      // Refresh after 1 day
    freshAge: 300,                // 5 min for sensitive ops
    cookieName: "nevr.session_token",
    cookie: {
      secure: true,
      httpOnly: true,
      sameSite: "lax",
      path: "/",
      domain: undefined,
    },
  },
})
OptionTypeDefaultDescription
expiresInnumber604800 (7d)Session TTL in seconds
updateAgenumber86400 (1d)Refresh threshold in seconds
freshAgenumber-"Fresh" session window for sensitive ops
cookieNamestringnevr.session_tokenSession cookie name
cookie.securebooleantrue in prodHTTPS only
cookie.httpOnlybooleantrueNo JS access
cookie.sameSitestringlaxCSRF protection
cookie.pathstring/Cookie path
cookie.domainstring-Cookie domain

Email Verification

typescript
auth({
  emailVerification: {
    enabled: true,
    expiresIn: 3600,
    sendOnSignUp: true,
    autoSignInAfterVerification: true,
    sendVerificationEmail: async ({ user, url, token }) => {
      await sendEmail(user.email, `Verify: ${url}`)
    },
    onEmailVerification: async (user) => {
      console.log(`${user.email} verified`)
    },
  },
})

Password Hashing

typescript
auth({
  password: {
    cost: 100000,  // PBKDF2 iterations
    hash: async (password, cost) => customHash(password),
    verify: async (password, hash) => customVerify(password, hash),
  },
})

OAuth Providers

typescript
auth({
  socialProviders: {
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
      scope: ["email", "profile"],
      accessType: "offline",  // Get refresh token
      hd: "company.com",      // Restrict to domain
    },
    github: {
      clientId: process.env.GITHUB_CLIENT_ID,
      clientSecret: process.env.GITHUB_CLIENT_SECRET,
      scope: ["user:email"],
      allowSignup: true,
    },
    apple: {
      clientId: process.env.APPLE_SERVICE_ID,
      clientSecret: process.env.APPLE_SECRET,
      appBundleIdentifier: "com.yourapp",
    },
  },
})

Account Linking

typescript
auth({
  account: {
    accountLinking: {
      enabled: true,
      trustedProviders: ["google"],  // Auto-link these providers
      allowDifferentEmails: false,
    },
  },
})

Extending User Schema

typescript
auth({
  user: {
    additionalFields: {
      phone: { type: "string", required: false, input: true, returned: true },
      role: { type: "string", required: false, input: false, returned: true },
      premium: { type: "boolean", required: false, input: false, returned: true },
    },
  },
})

Generated Entities

User

FieldTypeDescription
idstringUUID
emailstringUnique, lowercase, trimmed
namestringDisplay name
emailVerifiedbooleanVerification status
imagestring?Avatar URL
createdAtDateCreation timestamp
updatedAtDateLast update

Session

FieldTypeDescription
idstringUUID
tokenstringSecure session token (hidden)
userIdstringUser reference
expiresAtDateExpiration time
ipAddressstring?Client IP
userAgentstring?Browser info
createdAtDateCreation time

Account

FieldTypeDescription
idstringUUID
userIdstringUser reference
providerIdstring"credential", "google", "github", etc.
accountIdstringProvider user ID
passwordstring?Hashed password (credential only)
accessTokenstring?OAuth access token (hidden)
refreshTokenstring?OAuth refresh token (hidden)
expiresAtDate?Token expiration
scopestring?Granted scopes

Verification

FieldTypeDescription
idstringUUID
identifierstringPurpose (email, password-reset)
valuestringToken value (hidden)
expiresAtDateToken expiration

API Endpoints

Authentication

MethodPathDescription
POST/auth/sign-up/emailCreate account with email/password
POST/auth/sign-in/emailSign in with email/password
POST/auth/sign-outSign out (clear session)

Session Management

MethodPathDescription
GET/auth/sessionGet current session + user
GET/auth/list-sessionsList all user sessions
POST/auth/revoke-sessionRevoke specific session
POST/auth/revoke-sessionsRevoke all sessions
POST/auth/revoke-other-sessionsRevoke all except current

Email Verification

MethodPathDescription
POST/auth/send-verification-emailSend verification email
GET/auth/verify-emailVerify email token

Password Reset

MethodPathDescription
POST/auth/request-password-resetRequest reset email
GET/auth/reset-password/callbackVerify reset token
POST/auth/reset-passwordSet new password

User Management

MethodPathDescription
POST/auth/update-userUpdate name, image, etc.
POST/auth/change-passwordChange password (requires current)
POST/auth/change-emailChange email (sends verification)
POST/auth/delete-userDelete account permanently

Account Management

MethodPathDescription
GET/auth/list-accountsList linked providers
POST/auth/unlink-accountUnlink a provider

OAuth

MethodPathDescription
POST/auth/sign-in/:providerStart OAuth flow
GET/auth/callback/:providerHandle OAuth callback
POST/auth/link-socialLink OAuth to existing account

Client Methods

typescript
import { createTypedClient } from "nevr/client"
import { authClient } from "nevr/plugins/auth/client"
import type { API } from "./server/api"

const client = createTypedClient<API>({
  baseURL: "http://localhost:3000",
  basePath: "/api",
  plugins: [authClient()],
})

// Authentication
await client.auth.signUp.email({ email, password, name })
await client.auth.signIn.email({ email, password })
await client.auth.signOut()

// Session
const { user, session } = await client.auth.getSession()
const sessions = await client.auth.listSessions()
await client.auth.revokeSession({ token })
await client.auth.revokeOtherSessions()

// Profile
await client.auth.updateUser({ name: "New Name" })
await client.auth.changePassword({ currentPassword, newPassword })
await client.auth.changeEmail({ newEmail })
await client.auth.deleteUser({ password })

// Accounts
const { accounts } = await client.auth.listAccounts()
await client.auth.unlinkAccount({ providerId: "google" })

// OAuth
const { url } = await client.auth.signIn.social({ provider: "google" })
await client.auth.linkSocial({ providerId: "github" })

Hooks

Register callbacks for auth events:

typescript
import { registerAuthHooks } from "nevr/plugins/auth"

registerAuthHooks({
  beforeCreateUser: async (data) => {
    data.role = "member"
    return data
  },
  afterCreateUser: async (user) => {
    await sendWelcomeEmail(user.email)
  },
  beforeSignIn: async (email) => {
    // Rate limiting, etc.
  },
  afterSignIn: async (user, session) => {
    await logSignIn(user.id, session.ipAddress)
  },
  beforeCreateSession: async (userId) => {},
  afterCreateSession: async (session) => {},
  beforeSignOut: async (userId) => {},
  afterSignOut: async (userId) => {},
  afterEmailVerified: async (user) => {},
})

Error Codes

CodeMessage
USER_ALREADY_EXISTSEmail already registered
INVALID_EMAILInvalid email format
PASSWORD_TOO_SHORTPassword below minimum length
PASSWORD_TOO_LONGPassword above maximum length
INVALID_EMAIL_OR_PASSWORDWrong credentials
EMAIL_NOT_VERIFIEDEmail verification required
ACCOUNT_NOT_FOUNDNo account found
CREDENTIAL_ACCOUNT_NOT_FOUNDNo password set
SESSION_NOT_FOUNDInvalid session
SESSION_EXPIREDSession has expired
SESSION_NOT_FRESHSession too old for sensitive op
VERIFICATION_TOKEN_EXPIREDToken has expired
INVALID_VERIFICATION_TOKENInvalid token
UNAUTHORIZEDNot authenticated
FORBIDDENNot authorized

Security Features

  • PKCE for OAuth (RFC 7636)
  • AES-256-GCM encrypted state
  • PBKDF2 password hashing (100k iterations)
  • Fresh sessions for sensitive operations
  • Automatic token expiry (10 min for OAuth state)
  • Session binding to IP/User-Agent

Next Steps

Released under the MIT License.