Skip to main content

@zewstid/nextjs

Official ZewstID SDK for Next.js - Complete authentication solution with 12 authentication methods in one simple package.

npm version License: Proprietary

Current Version: v0.7.3 (January 2026)

✨ Features

Authentication Methods (v0.7.3)

  • šŸ” OAuth/OIDC - Full OpenID Connect with ZewstID
  • šŸ“§ Email/Password - Traditional username/password authentication with password reset
  • ✨ Magic Links - Passwordless authentication via email
  • šŸ“± OTP - One-time passwords via email or SMS
  • šŸ”‘ WebAuthn/Passkeys - Biometric and hardware key authentication
  • šŸ›”ļø Multi-Factor Auth (MFA) - TOTP authenticator app support
  • 🌐 Social Login - Google, GitHub, Microsoft shortcuts
  • šŸ“² Device Enrollment - QR code enrollment for authenticator apps (v0.5.0+)
  • šŸ”§ Device Management - List and manage authenticator devices (v0.5.0+)

Developer Experience

  • ⚔ All-in-One - 12 authentication methods in one package
  • šŸ”— Cross-Portal SSO - Enable seamless SSO across Zewst apps (v0.7.0+)
  • šŸŽÆ Type-Safe - Full TypeScript support with excellent IntelliSense
  • šŸ“¦ Lightweight - Only 51KB (15KB gzipped) with tree-shaking
  • šŸš€ Zero Dependencies - No external packages required
  • šŸ”„ 100% Compatible - Works with existing v0.1.x code
  • šŸ“± Next.js 14/15 - Full App Router and Pages Router support
  • šŸŽØ Easy to Use - Simple, intuitive API

šŸš€ Installation

npm install @zewstid/nextjs next-auth

šŸ“– Quick Start

1. Get Your Credentials

Sign up at developers.zewstid.com and create a new application to get your:

  • Client ID
  • Client Secret (optional for public clients)

2. Environment Variables

Create a

.env.local
file:

# Required NEXT_PUBLIC_ZEWSTID_CLIENT_ID=your_client_id ZEWSTID_CLIENT_SECRET=your_client_secret # NextAuth (for OAuth) NEXTAUTH_SECRET=your_random_secret_min_32_chars NEXTAUTH_URL=http://localhost:3000 # Optional ZEWSTID_API_URL=https://api.zewstid.com/api/v1

3. Create Auth Configuration

App Router (

app/api/auth/[...nextauth]/route.ts
):

import { createZewstIDAuth } from '@zewstid/nextjs'; import NextAuth from 'next-auth'; export const authOptions = createZewstIDAuth({ clientId: process.env.NEXT_PUBLIC_ZEWSTID_CLIENT_ID!, clientSecret: process.env.ZEWSTID_CLIENT_SECRET!, }); const handler = NextAuth(authOptions); export { handler as GET, handler as POST };

4. Wrap Your App

App Router (

app/layout.tsx
):

import { ZewstIDProvider } from '@zewstid/nextjs'; export default function RootLayout({ children }) { return ( <html> <body> <ZewstIDProvider> {children} </ZewstIDProvider> </body> </html> ); }

5. Use Authentication

'use client'; import { useZewstID } from '@zewstid/nextjs'; export function AuthDemo() { const { user, signIn, signOut, signInWithPassword, sendMagicLink, sendOTP, verifyOTP, webauthn, } = useZewstID({ clientId: process.env.NEXT_PUBLIC_ZEWSTID_CLIENT_ID!, }); if (!user) { return ( <div> {/* OAuth */} <button onClick={() => signIn()}> Sign in with ZewstID </button> {/* Password */} <button onClick={() => signInWithPassword('user@example.com', 'pass')}> Sign in with Password </button> {/* Magic Link */} <button onClick={() => sendMagicLink('user@example.com')}> Send Magic Link </button> {/* OTP */} <button onClick={() => sendOTP('user@example.com')}> Send OTP Code </button> {/* WebAuthn */} <button onClick={() => webauthn.authenticate()}> šŸ” Use Touch ID </button> </div> ); } return ( <div> <p>Welcome {user.name}!</p> <button onClick={() => signOut()}>Sign Out</button> </div> ); }

šŸ”— Cross-Portal SSO (v0.7.0+)

For internal Zewst ecosystem applications that need to share authentication sessions across portals (User Portal, Developer Portal, Admin Dashboard, etc.), enable SSO establishment:

Setup

Create

app/api/zewstid/[...zewstid]/route.ts
:

import { createZewstIDHandlers } from '@zewstid/nextjs/handlers'; const handlers = createZewstIDHandlers({ clientId: process.env.ZEWSTID_CLIENT_ID!, clientSecret: process.env.ZEWSTID_CLIENT_SECRET!, baseUrl: process.env.NEXTAUTH_URL, apiUrl: process.env.ZEWSTID_API_URL || 'https://api.zewstid.com', issuerUrl: process.env.ZEWSTID_ISSUER_URL || 'https://auth.zewstid.com/realms/zewstid', enableSSOEstablishment: true, // Enable cross-portal SSO }); export const GET = handlers.GET; export const POST = handlers.POST;

How It Works

When

enableSSOEstablishment: true
:

  1. User authenticates via password/OTP/magic-link
  2. SDK validates credentials via API Gateway (ROPC flow)
  3. On success, SDK requests an SSO token
  4. User is silently redirected to ZewstID with the token
  5. ZewstID validates and creates a browser session
  6. User is redirected back with OAuth code
  7. NextAuth exchanges code for session

Result: User can navigate to any Zewst portal and be automatically authenticated.

When to Use

ScenarioUse SSOWhy
Internal Zewst appYesCross-portal SSO needed
Third-party integrationNoStandard OAuth is simpler
Partner appNoDon't need Zewst SSO
Testing/developmentOptionalDepends on requirements

Available Handlers with SSO

The

createZewstIDHandlers()
function provides these endpoints:

EndpointMethodDescription
/api/zewstid/check-email
GET/POSTCheck if user exists
/api/zewstid/methods
POSTGet available auth methods
/api/zewstid/password
POSTPassword authentication
/api/zewstid/otp/send
POSTSend OTP code
/api/zewstid/otp/verify
POSTVerify OTP code
/api/zewstid/magic-link/send
POSTSend magic link
/api/zewstid/magic-link/verify
POSTVerify magic link
/api/zewstid/mfa/verify
POSTVerify MFA/TOTP code
/api/zewstid/establish-sso
GETEstablish ZewstID SSO
/api/zewstid/sso-callback
GETHandle SSO callback

⚔ Best Practices

Backend Authentication with Tokens

Always use ID tokens for backend authentication, not access tokens:

// āœ… CORRECT - Use ID token for your backend const response = await fetch('/api/protected', { headers: { 'Content-Type': 'application/json', Authorization: session.idToken ? `Bearer ${session.idToken}` : '', }, }); // Decode ID token in your backend const payload = JSON.parse( Buffer.from(session.idToken.split('.')[1], 'base64url').toString() ); console.log('User ID:', payload.sub); console.log('Email:', payload.email);
// āŒ WRONG - Don't use access token for your own backend const response = await fetch('/api/protected', { headers: { Authorization: session.accessToken ? `Bearer ${session.accessToken}` : '', }, }); // This may fail because access tokens can be opaque (not JWTs)

Why Use ID Tokens?

  • āœ… ID tokens are always JWTs (decodable)
  • āœ… ID tokens contain user claims (email, name, roles)
  • āœ… ID tokens are designed for user authentication
  • āŒ Access tokens may be opaque (not decodable)
  • āŒ Access tokens are designed for calling external APIs

When to Use Each Token:

Token TypeUse ForExample
ID TokenAuthenticating to YOUR backend
Authorization: Bearer ${session.idToken}
Access TokenCalling EXTERNAL APIsCalling Google APIs, GitHub API, etc.

For more details, see: Token Type Troubleshooting

Session Management

Session contains both tokens (v0.4.6+):

const { data: session } = useSession(); console.log(session.idToken); // āœ… Always available (v0.4.6+) console.log(session.accessToken); // āœ… Available for external APIs console.log(session.user); // āœ… User information

Migration from v0.4.5: If upgrading from v0.4.5 or earlier, update your backend to use

session.idToken
. Users will need to log out and back in once after the upgrade.

šŸ“š Authentication Methods

1ļøāƒ£ OAuth/OIDC (Default)

Redirect users to ZewstID for authentication:

const { signIn, signOut, user } = useZewstID({ clientId: process.env.NEXT_PUBLIC_ZEWSTID_CLIENT_ID!, }); // Sign in await signIn({ callbackUrl: '/dashboard' }); // Sign out await signOut({ callbackUrl: '/' });

2ļøāƒ£ Email/Password

Traditional username/password authentication:

const { signInWithPassword, signUpWithPassword } = useZewstID({ clientId: process.env.NEXT_PUBLIC_ZEWSTID_CLIENT_ID!, }); // Sign up const { userId } = await signUpWithPassword( 'user@example.com', 'SecurePass123!', { firstName: 'John', lastName: 'Doe', sendVerificationEmail: true, } ); // Sign in await signInWithPassword('user@example.com', 'SecurePass123!');

Password Reset Flow

Complete password reset with CAPTCHA protection:

const { forgotPassword, verifyResetToken, resetPassword } = useZewstID({ clientId: process.env.NEXT_PUBLIC_ZEWSTID_CLIENT_ID!, }); // Step 1: Request password reset email await forgotPassword('user@example.com', { captchaToken: captchaToken, // From CAPTCHA component redirectUrl: '/reset-password', // Optional custom reset URL }); // Step 2: (Optional) Verify token validity before showing form const { valid, email, expiresIn } = await verifyResetToken(tokenFromURL); // Step 3: Reset password with token from email await resetPassword(tokenFromURL, 'NewSecurePass123!'); // All user sessions are automatically invalidated after reset

Security Features:

  • āœ… Tokens expire in 1 hour
  • āœ… Single-use tokens (can't be reused)
  • āœ… Rate limiting: 3 requests/hour per email, 10/hour per IP
  • āœ… CAPTCHA protection against bots
  • āœ… All existing sessions invalidated after reset
  • āœ… Email enumeration prevention

See complete example for full UI implementation with error handling.

Passwordless email authentication:

const { sendMagicLink, verifyMagicLink } = useZewstID({ clientId: process.env.NEXT_PUBLIC_ZEWSTID_CLIENT_ID!, }); // Send magic link (auto-creates user if doesn't exist) await sendMagicLink('user@example.com', { redirectUrl: '/dashboard', }); // For security-conscious apps: Only send to existing users await sendMagicLink('user@example.com', { createIfNotExists: false, // Returns 404 if user doesn't exist }); // Verify (in callback page) await verifyMagicLink(tokenFromURL);

4ļøāƒ£ OTP Authentication

Email or SMS one-time passwords:

const { sendOTP, verifyOTP } = useZewstID({ clientId: process.env.NEXT_PUBLIC_ZEWSTID_CLIENT_ID!, }); // Send OTP (auto-creates user if doesn't exist) const { expiresIn } = await sendOTP('user@example.com', { provider: 'email', // or 'sms' }); // For security-conscious apps: Only send to existing users await sendOTP('user@example.com', { provider: 'email', createIfNotExists: false, // Returns 404 if user doesn't exist }); // Verify OTP await verifyOTP('user@example.com', '123456');

5ļøāƒ£ WebAuthn/Passkeys

Biometric and hardware key authentication:

const { webauthn } = useZewstID({ clientId: process.env.NEXT_PUBLIC_ZEWSTID_CLIENT_ID!, }); // Register credential (requires existing session) await webauthn.register({ displayName: 'Touch ID', authenticatorAttachment: 'platform', }); // Authenticate await webauthn.authenticate('user@example.com'); // Manage credentials const credentials = await webauthn.listCredentials(); await webauthn.deleteCredential(credentialId);

6ļøāƒ£ Multi-Factor Authentication

TOTP authenticator app support:

const { mfa } = useZewstID({ clientId: process.env.NEXT_PUBLIC_ZEWSTID_CLIENT_ID!, }); // Enroll in MFA const { secret, qrCode } = await mfa.enrollTOTP({ displayName: 'My Authenticator', }); // Verify enrollment await mfa.verifyTOTPEnrollment('123456'); // Disable MFA await mfa.disable();

7ļøāƒ£ Social Login

Quick shortcuts for popular providers:

const { signInWithGoogle, signInWithGitHub, signInWithMicrosoft } = useZewstID({ clientId: process.env.NEXT_PUBLIC_ZEWSTID_CLIENT_ID!, }); await signInWithGoogle(); await signInWithGitHub(); await signInWithMicrosoft();

8ļøāƒ£ Account Linking (v0.3.0+)

Link multiple authentication methods to a single account:

const { accountLinking } = useZewstID({ clientId: process.env.NEXT_PUBLIC_ZEWSTID_CLIENT_ID!, }); // When user tries to sign in with a new method using existing email // The API returns 409 Conflict with linking suggestion // Step 1: Initiate linking const { linkingToken } = await accountLinking.initiate({ email: 'user@example.com', newProvider: 'google', // or 'magic_link', 'github', etc. }); // Step 2: Verify ownership (password OR email code) await accountLinking.verify({ linkingToken: linkingToken, password: 'user-password', // OR use code from email }); // Convenience methods await accountLinking.linkWithPassword('user@example.com', 'google', 'password'); await accountLinking.linkWithCode('user@example.com', 'google', '123456'); // Manage linked identities const { identities } = await accountLinking.getLinkedIdentities(); // Returns: ['password', 'google', 'github', 'magic_link'] await accountLinking.unlink('google'); // Remove authentication method const linked = await accountLinking.isLinked('github'); // Check if linked

Security Features:

  • āœ… Ownership verification required (password OR email code)
  • āœ… Cannot unlink last authentication method
  • āœ… Rate limiting on linking endpoints
  • āœ… Audit logging for security monitoring
  • āœ… 10-minute token expiration

Use Cases:

  • Password user wants to add Google login
  • OAuth user wants to set a password
  • Link magic links, OTP, WebAuthn to existing account
  • Single identity across all authentication methods

9ļøāƒ£ Device Enrollment (v0.5.0+)

Allow users to enroll authenticator devices by scanning QR codes:

const { useEnrollment } = require('@zewstid/nextjs'); function EnrollDevicePage() { const { startEnrollment, checkStatus, enrollmentSession, status, isLoading, error, } = useEnrollment({ clientId: process.env.NEXT_PUBLIC_ZEWSTID_CLIENT_ID!, accessToken: session?.accessToken, }); const handleStartEnrollment = async () => { const session = await startEnrollment({ accountName: 'My Work Account', iconUrl: 'https://myapp.com/icon.png', // Optional }); // Display session.qrCode to user console.log('Enrollment ID:', session.enrollmentId); }; // Poll for completion useEffect(() => { if (enrollmentSession) { const interval = setInterval(async () => { const currentStatus = await checkStatus(enrollmentSession.enrollmentId); if (currentStatus.status === 'completed') { clearInterval(interval); alert('Device enrolled successfully!'); } }, 2000); return () => clearInterval(interval); } }, [enrollmentSession]); return ( <div> <button onClick={handleStartEnrollment} disabled={isLoading}> {isLoading ? 'Starting...' : 'Enroll New Device'} </button> {enrollmentSession && ( <div> <h3>Scan this QR code with your authenticator app</h3> <img src={enrollmentSession.qrCode} alt="Enrollment QR Code" /> <p>Waiting for device to scan...</p> </div> )} {error && <p className="error">{error}</p>} </div> ); }

šŸ”Ÿ Authenticator Device Management (v0.5.0+)

List and manage enrolled authenticator devices:

const { useAuthenticatorDevices } = require('@zewstid/nextjs'); function DevicesPage() { const { devices, isLoading, error, fetchDevices, removeDevice, isRemoving, } = useAuthenticatorDevices({ clientId: process.env.NEXT_PUBLIC_ZEWSTID_CLIENT_ID!, accessToken: session?.accessToken, }); const handleRemove = async (deviceId: string) => { if (confirm('Remove this device?')) { await removeDevice(deviceId); } }; if (isLoading) return <p>Loading devices...</p>; return ( <div> <h2>Your Authenticator Devices</h2> {devices.length === 0 ? ( <p>No devices enrolled. Add one to enable push authentication.</p> ) : ( <ul> {devices.map((device) => ( <li key={device.id}> <span>{device.name}</span> <span>{device.platform} - {device.model}</span> <span>Last active: {device.lastActive}</span> <button onClick={() => handleRemove(device.id)} disabled={isRemoving} > Remove </button> </li> ))} </ul> )} <button onClick={fetchDevices}>Refresh</button> </div> ); }

1ļøāƒ£1ļøāƒ£ TOTP Setup (v0.5.0+)

Set up TOTP for authenticator apps:

const { useTOTPSetup } = require('@zewstid/nextjs'); function TOTPSetupPage() { const { setupTOTP, verifyTOTP, removeTOTP, regenerateBackupCodes, config, isConfigured, isLoading, error, } = useTOTPSetup({ clientId: process.env.NEXT_PUBLIC_ZEWSTID_CLIENT_ID!, accessToken: session?.accessToken, accountId: user?.id, }); const [code, setCode] = useState(''); const handleSetup = async () => { const result = await setupTOTP({ accountName: user?.email || 'My Account', }); // Display QR code and backup codes to user console.log('Scan this QR:', result.qrCode); console.log('Backup codes:', result.backupCodes); }; const handleVerify = async () => { const result = await verifyTOTP(code); if (result.success) { alert('TOTP verified and enabled!'); } }; return ( <div> {!isConfigured ? ( <> <button onClick={handleSetup} disabled={isLoading}> Set Up Authenticator App </button> {config?.qrCode && ( <div> <img src={config.qrCode} alt="TOTP QR Code" /> <p>Manual entry: {config.secret}</p> <h4>Backup Codes (save these!)</h4> <ul> {config.backupCodes?.map((code, i) => ( <li key={i}><code>{code}</code></li> ))} </ul> <input type="text" value={code} onChange={(e) => setCode(e.target.value)} placeholder="Enter 6-digit code" maxLength={6} /> <button onClick={handleVerify}>Verify & Enable</button> </div> )} </> ) : ( <div> <p>āœ… TOTP is enabled</p> <button onClick={regenerateBackupCodes}> Regenerate Backup Codes </button> <button onClick={removeTOTP} className="danger"> Disable TOTP </button> </div> )} {error && <p className="error">{error}</p>} </div> ); }

1ļøāƒ£2ļøāƒ£ OIDC Logout (v0.4.2+)

Proper OpenID Connect logout that clears both app and SSO sessions:

Create logout endpoint (

app/api/auth/logout/route.ts
):

import { createLogoutHandler } from '@zewstid/nextjs/handlers'; import { authOptions } from '@/lib/auth'; export const GET = createLogoutHandler(authOptions, { postLogoutRedirectUri: 'https://myapp.com', // Optional });

Update sign-out buttons:

'use client'; export function SignOutButton() { const handleSignOut = () => { // Use logout endpoint instead of signOut() window.location.href = '/api/auth/logout'; }; return <button onClick={handleSignOut}>Sign Out</button>; }

Optional: Post-logout cleanup handler (

app/api/auth/post-logout/route.ts
):

import { createPostLogoutHandler } from '@zewstid/nextjs/handlers'; export const GET = createPostLogoutHandler({ redirectTo: '/', // Redirect after cleanup });

What this fixes:

  • āŒ Before v0.4.2: Users were auto-logged back in after sign out
  • āœ… After v0.4.2: Proper OIDC logout clears SSO cookies
  • Users must re-enter credentials after logout
  • Follows OpenID Connect RP-Initiated Logout spec

šŸŽÆ API Reference

Client Hooks

useZewstID(config?)

Main authentication hook with all methods:

const { // State user, // Current user session, // Full session with tokens isAuthenticated, // Boolean auth status isLoading, // Boolean loading state error, // Any auth errors // OAuth signIn, // Sign in with OAuth signOut, // Sign out refreshToken, // Refresh access token // Password signInWithPassword, signUpWithPassword, forgotPassword, // Request password reset email verifyResetToken, // Verify reset token validity resetPassword, // Reset password with token // Magic Link sendMagicLink, verifyMagicLink, // OTP sendOTP, verifyOTP, // WebAuthn webauthn: { register, authenticate, listCredentials, deleteCredential, }, // MFA mfa: { enrollTOTP, verifyTOTPEnrollment, disable, }, // Social signInWithGoogle, signInWithGitHub, signInWithMicrosoft, // Account Linking (v0.3.0+) accountLinking: { initiate, verify, getLinkedIdentities, unlink, isLinked, linkWithPassword, linkWithCode, }, } = useZewstID({ clientId: 'your_client_id', apiUrl: 'https://api.zewstid.com/api/v1', // optional });

useEnrollment(config)
(v0.5.0+)

Hook for device enrollment via QR code:

const { startEnrollment, // Start new enrollment session checkStatus, // Check enrollment status enrollmentSession, // Current session (qrCode, enrollmentId, expiresAt) status, // 'pending' | 'completed' | 'expired' isLoading, error, } = useEnrollment({ clientId: 'your_client_id', accessToken: session?.accessToken, });

useAuthenticatorDevices(config)
(v0.5.0+)

Hook for managing enrolled authenticator devices:

const { devices, // Array of AuthenticatorDevice isLoading, error, fetchDevices, // Refresh device list removeDevice, // Remove a device by ID isRemoving, } = useAuthenticatorDevices({ clientId: 'your_client_id', accessToken: session?.accessToken, });

useTOTPSetup(config)
(v0.5.0+)

Hook for TOTP authenticator setup and management:

const { setupTOTP, // Start TOTP setup (returns qrCode, secret, backupCodes) verifyTOTP, // Verify code and enable TOTP removeTOTP, // Disable TOTP regenerateBackupCodes, // Get new backup codes config, // Current TOTP config (if enabled) isConfigured, // Whether TOTP is already set up isLoading, error, } = useTOTPSetup({ clientId: 'your_client_id', accessToken: session?.accessToken, accountId: user?.id, });

useUser()

Shorthand to get current user:

const user = useUser();

useAuth()

Get authentication status:

const { isAuthenticated, isLoading, error } = useAuth();

useAccessToken()

Get access token for API calls:

const accessToken = useAccessToken();

Server-Side Client

For server-side or API route usage:

import { createZewstID } from '@zewstid/nextjs'; const client = createZewstID({ clientId: process.env.ZEWSTID_CLIENT_ID!, clientSecret: process.env.ZEWSTID_CLIENT_SECRET!, }); // Use any authentication method const session = await client.signInWithPassword(email, password); await client.sendMagicLink(email); const { expiresIn } = await client.sendOTP(email);

Logout Handlers (v0.4.2+)

For OIDC-compliant logout in API routes:

import { createLogoutHandler, createPostLogoutHandler } from '@zewstid/nextjs/handlers'; import { authOptions } from '@/lib/auth'; // Main logout handler (required) export const GET = createLogoutHandler(authOptions, { postLogoutRedirectUri: 'https://myapp.com', }); // Optional post-logout cleanup export const POST = createPostLogoutHandler({ redirectTo: '/', onLogout: async (session) => { // Custom cleanup logic console.log('User logged out:', session.user.email); }, });

šŸ“¦ TypeScript

Full TypeScript support included:

import type { ZewstIDUser, ZewstIDSession, ZewstIDConfig, UseZewstIDReturn, PasswordSignInOptions, PasswordSignUpOptions, MagicLinkSendOptions, OTPSendOptions, WebAuthnRegisterOptions, WebAuthnCredential, TOTPEnrollOptions, // Account Linking Types (v0.3.0+) LinkAccountOptions, VerifyLinkingOptions, LinkingInitiateResponse, LinkingVerifyResponse, LinkedIdentitiesResponse, UnlinkResponse, // Logout Handler Types (v0.4.2+) LogoutHandlerOptions, PostLogoutHandlerOptions, // Device Enrollment Types (v0.5.0+) EnrollmentSession, EnrollmentStatus, StartEnrollmentOptions, AuthenticatorDevice, // TOTP Setup Types (v0.5.0+) TOTPSetupOptions, TOTPSetupResponse, TOTPVerifyOptions, TOTPVerifyResponse, TOTPConfigResponse, BackupCodesResponse, } from '@zewstid/nextjs';

šŸ”„ Migration from v0.1.9

No breaking changes! Your existing code continues to work:

// v0.1.9 code - still works! āœ… const { user, signIn, signOut } = useZewstID();

To use new authentication methods, just pass the config:

// v0.2.0 - enhanced with new methods const { user, signIn, signOut, signInWithPassword, // NEW sendMagicLink, // NEW webauthn, // NEW } = useZewstID({ clientId: process.env.NEXT_PUBLIC_ZEWSTID_CLIENT_ID!, });

šŸ“Š Bundle Size

PackageSize (Uncompressed)Size (Gzipped)
Main51 KB~15 KB
Handlers4.5 KB~2 KB
Middleware7.6 KB~3 KB
Server11 KB~4 KB

Total: ~24 KB gzipped for full package with all features.

With tree-shaking, apps using only OAuth will be ~8-10 KB gzipped.

šŸ›”ļø Security

Built-in Security Features

  • āœ… Secure token storage (httpOnly cookies via NextAuth)
  • āœ… CSRF protection built-in
  • āœ… PKCE for OAuth flows
  • āœ… Proper session management
  • āœ… WebAuthn with public key cryptography
  • āœ… TOTP for multi-factor authentication
  • āœ… Email verification required for all new users
  • āœ… Rate limiting (IP-based and per-email)

User Creation Control

By default, passwordless authentication methods (magic links, OTP) auto-create users for convenience. This matches industry standards (Supabase, Auth0, Clerk).

For security-conscious applications, you can require explicit registration:

// Only send magic links to existing users await sendMagicLink('user@example.com', { createIfNotExists: false // Returns 404 if user doesn't exist }); // Only send OTP to existing users await sendOTP('user@example.com', { createIfNotExists: false });

Best Practice: Use auto-creation (

createIfNotExists: true
, the default) combined with email verification:

// 1. User requests magic link (auto-creates account) await sendMagicLink('user@example.com'); // 2. User clicks link, email is verified // 3. Require email verification before allowing app access if (!user.emailVerified) { return redirect('/verify-email'); }

Rate Limiting

ZewstID includes multi-layer rate limiting to prevent abuse:

  • IP-based: 10 requests per 15 minutes per IP
  • Email-based: 5 magic links/OTPs per hour per email
  • Strict limits: 5 requests per 15 minutes for sensitive operations

This prevents:

  • āŒ Email bombing
  • āŒ Account enumeration
  • āŒ Brute force attacks
  • āŒ Denial of service

🌟 Examples

Complete Login Page

'use client'; import { useZewstID } from '@zewstid/nextjs'; import { useState } from 'react'; export default function LoginPage() { const { signIn, signInWithPassword, sendMagicLink, signInWithGoogle, } = useZewstID({ clientId: process.env.NEXT_PUBLIC_ZEWSTID_CLIENT_ID!, }); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); return ( <div> <h1>Sign In</h1> {/* OAuth */} <button onClick={() => signIn()}> Sign in with ZewstID </button> {/* Social */} <button onClick={signInWithGoogle}> Sign in with Google </button> {/* Password */} <form onSubmit={(e) => { e.preventDefault(); signInWithPassword(email, password); }}> <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Email" /> <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Password" /> <button type="submit">Sign In with Password</button> </form> {/* Magic Link */} <button onClick={() => sendMagicLink(email)}> Send Magic Link </button> </div> ); }

Protected Server Component

import { getServerSession } from 'next-auth'; import { authOptions } from '@/app/api/auth/[...nextauth]/route'; import { redirect } from 'next/navigation'; export default async function DashboardPage() { const session = await getServerSession(authOptions); if (!session) { redirect('/login'); } return ( <div> <h1>Welcome {session.user.name}!</h1> <p>Email: {session.user.email}</p> <p>Roles: {session.user.roles?.join(', ')}</p> </div> ); }

šŸ“– Documentation

šŸ†˜ Support

šŸ“œ License

Proprietary and Confidential

Copyright Ā© 2025 Zewst, Inc. All Rights Reserved.

This software is proprietary and confidential. Unauthorized distribution or use is prohibited.

šŸ™ Credits

Built with:


Made with ā¤ļø by the Zewst Team

Was this page helpful?

Let us know how we can improve our documentation