Skip to main content

SDK Changelog

Complete release history for the ZewstID Next.js SDK (@zewstid/nextjs)

SDK Release v0.4.6 - ID Token Storage Fix

Release Date: December 28, 2025 Package:

@zewstid/nextjs
Version: 0.4.6 Type: ๐Ÿ”ด Critical Bugfix Installation:
npm install @zewstid/nextjs@0.4.6


๐Ÿšจ Critical Fix: ID Token Storage in
getZewstIDCallbacks()

The Problem

Versions v0.4.5 and earlier had a critical inconsistency in the SDK where ID tokens were not being stored when using

getZewstIDCallbacks()
:

getZewstIDCallbacks() โ†’ โŒ No ID token stored createZewstIDAuth() โ†’ โœ… ID token stored

This affected:

  • โœ… Backend authentication - Apps couldn't decode user tokens
  • โœ… OIDC logout - Logout handler needs
    id_token_hint
  • โœ… SDK consistency - Different behavior between two auth methods

The Root Cause

The

getZewstIDCallbacks()
function in
src/auth.ts
was only storing the access token, not the ID token:

// โŒ BEFORE (v0.4.5) export function getZewstIDCallbacks(clientId: string) { return { async jwt({ token, account }: any) { if (account && account.id_token) { // ... extract user data from ID token // Store ONLY the access token (needed for API calls) // Don't store ID token or refresh token in session cookie token.accessToken = account.access_token; // โŒ ID token NOT stored! } return token; }, async session({ session, token }: any) { // ... session.accessToken = token.accessToken; // โŒ ID token NOT available! return session; }, }; }

This caused apps to fail when trying to:

  1. Authenticate users to their backend (access tokens may be opaque)
  2. Perform OIDC logout (requires
    id_token_hint
    )
  3. Decode user information from tokens (access tokens lack user claims)

The Fix

// โœ… AFTER (v0.4.6) export function getZewstIDCallbacks(clientId: string) { return { async jwt({ token, account }: any) { if (account && account.id_token) { // ... extract user data from ID token // Store ONLY the access token (needed for API calls) token.accessToken = account.access_token; // โœ… Store ID token for proper OIDC logout and backend authentication token.idToken = account.id_token; } return token; }, async session({ session, token }: any) { // ... session.accessToken = token.accessToken; // โœ… Make ID token available for proper logout and backend authentication session.idToken = token.idToken; return session; }, }; }

๐Ÿ”ง What's Fixed

Before vs After

Featurev0.4.5v0.4.6
ID Token StorageโŒ Missingโœ… Stored
Backend AuthโŒ Fails (opaque access token)โœ… Works (JWT ID token)
OIDC Logoutโš ๏ธ Partial (no
id_token_hint
)
โœ… Complete
Consistencyโš ๏ธ Different from
createZewstIDAuth()
โœ… Consistent

Affected Use Cases

Use CaseImpactStatus
Apps using
getZewstIDCallbacks()
Highโœ… Fixed
Backend token decodingHighโœ… Fixed
OIDC logout with
id_token_hint
Mediumโœ… Fixed
Apps using
createZewstIDAuth()
NoneAlready working

๐Ÿ“ฆ Installation

Upgrade from v0.4.5

npm install @zewstid/nextjs@0.4.6

Or if using the private registry:

npm install @zewstid/nextjs@0.4.6 --registry=https://npm.zewstid.com

Verify Installation

npm list @zewstid/nextjs # Should show: @zewstid/nextjs@0.4.6

๐Ÿ”„ Migration Guide

For Apps Using
getZewstIDCallbacks()

Step 1: Update the SDK

npm install @zewstid/nextjs@0.4.6

Step 2: Update Your Backend to Use ID Token

If your backend was trying to decode

session.accessToken
, update it to use
session.idToken
:

Before (v0.4.5):

// lib/api/client.ts const response = await fetch('/api/protected', { headers: { 'Content-Type': 'application/json', Authorization: session.accessToken ? `Bearer ${session.accessToken}` : '', }, });

After (v0.4.6):

// lib/api/client.ts const response = await fetch('/api/protected', { headers: { 'Content-Type': 'application/json', Authorization: session.idToken ? `Bearer ${session.idToken}` : '', // โ† CHANGED }, });

Step 3: Clear User Sessions

After deploying, users will need to log out and log back in to get a session with the ID token.

Optional: Add a migration notice:

'use client'; import { useSession } from 'next-auth/react'; import { signOut } from 'next-auth/react'; export function MigrationCheck() { const { data: session } = useSession(); // Check if session has ID token if (session && !session.idToken) { signOut(); // Or show: "Please log in again to continue" } return null; }

For Apps Using
createZewstIDAuth()

No changes needed - this function already stored ID tokens correctly.


๐Ÿงช Testing the Fix

Test 1: Verify ID Token is Available

'use client'; import { useSession } from 'next-auth/react'; export function SessionDebug() { const { data: session } = useSession(); console.log('Has access token:', !!session?.accessToken); console.log('Has ID token:', !!session?.idToken); // โœ… Should be true now return ( <div> <p>Access Token: {session?.accessToken ? 'Present' : 'Missing'}</p> <p>ID Token: {session?.idToken ? 'โœ… Present' : 'โŒ Missing'}</p> </div> ); }

Test 2: Verify Backend Can Decode ID Token

// app/api/test/route.ts import { getServerSession } from 'next-auth'; import { authOptions } from '@/lib/auth'; export async function GET() { const session = await getServerSession(authOptions) as any; if (!session?.idToken) { return Response.json({ error: 'No ID token' }, { status: 401 }); } // Decode ID token (base64url decode, no signature verification needed) const payload = JSON.parse( Buffer.from(session.idToken.split('.')[1], 'base64url').toString() ); return Response.json({ success: true, userId: payload.sub, email: payload.email, name: payload.name, }); }

Test 3: Test OIDC Logout

// 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', }); // โœ… This now works properly with id_token_hint

๐Ÿ” Understanding OAuth/OIDC Tokens

Token Types Explained

Token TypeFormatPurposeWhen to Use
Access TokenOpaque or JWTAPI resource accessCalling external APIs (Google, GitHub, etc.)
ID TokenAlways JWTUser identity claimsAuthenticating user in your backend
Refresh TokenOpaqueToken refreshGetting new access/ID tokens

Why This Matters

Access tokens are designed for calling third-party APIs and may be opaque (not decodable as JWTs). They typically don't contain user information.

ID tokens are designed for user authentication and are always JWTs with user claims like email, name, roles, etc.

Example: Decoding Tokens

Access Token (may be opaque):

{ "aud": "account", "typ": "Bearer", "azp": "your-client-id" // โŒ Missing: email, name, email_verified }

ID Token (always has user claims):

{ "aud": "your-client-id", "typ": "ID", "sub": "user-uuid", "email": "user@example.com", "email_verified": true, "name": "John Doe", "roles": ["user", "admin"] // โœ… Has all user information }

๐Ÿš€ Impact Assessment

Who Should Upgrade Immediately?

High Priority (Upgrade ASAP):

  • Applications using
    getZewstIDCallbacks()
    from SDK v0.4.5 or earlier
  • Applications sending tokens to their backend for authentication
  • Applications using the SDK's logout handlers
  • The ZEYE app and similar integrated applications

Medium Priority (Upgrade soon):

  • Applications planning to implement backend authentication
  • Applications wanting proper OIDC logout

Low Priority (Can defer):

  • Applications using
    createZewstIDAuth()
    (already has ID tokens)
  • Applications not using backend authentication

Breaking Changes

None. This is a pure bugfix with 100% backward compatibility:

  • โœ… All existing code continues to work
  • โœ…
    session.accessToken
    is still available
  • โœ… Only adds
    session.idToken
    , doesn't remove anything

However, you should update your backend to use

session.idToken
for authentication instead of
session.accessToken
for better compatibility.


๐Ÿ“ Changelog Summary

Fixed

  • Critical: ID token not stored in
    getZewstIDCallbacks()
    • Added
      token.idToken = account.id_token;
      in JWT callback
    • Added
      session.idToken = token.idToken;
      in session callback
    • Fixes backend authentication failures (opaque access tokens)
    • Fixes improper OIDC logout (missing
      id_token_hint
      )
    • Aligns behavior with
      createZewstIDAuth()

Changed

  • None (pure bugfix release)

Added

  • None (pure bugfix release)

Removed

  • None (pure bugfix release)

This fix resolves:

  1. "Cannot decode token" - Access tokens may be opaque
  2. "Missing user claims" - Access tokens don't have email/name
  3. "Logout doesn't clear SSO session" - Need
    id_token_hint
    for OIDC logout
  4. "Backend authentication fails" - Wrong token type sent to backend
  5. "Inconsistent SDK behavior" -
    getZewstIDCallbacks()
    vs
    createZewstIDAuth()

See TROUBLESHOOTING-TOKEN-TYPE-MISMATCH.md for detailed troubleshooting.



๐Ÿ†˜ Support

If you encounter any issues after upgrading:

  1. Clear browser cache and cookies
  2. Log out and log back in to get new session with ID token
  3. Check the troubleshooting guide: TROUBLESHOOTING-TOKEN-TYPE-MISMATCH.md
  4. Contact support:

โœ… Migration Checklist

  • Upgrade to
    @zewstid/nextjs@0.4.6
  • Update backend to use
    session.idToken
    instead of
    session.accessToken
  • Clear npm cache and reinstall dependencies
  • Test login flow
  • Test logout flow (should now use
    id_token_hint
    )
  • Test backend token decoding
  • Verify all auth methods work
  • Deploy to production
  • Notify users to log out and back in
  • Monitor error logs

This is a critical bugfix for backend authentication and OIDC logout. We strongly recommend upgrading as soon as possible.

Thank you for using ZewstID! ๐ŸŽ‰


Released on December 28, 2025 by the ZewstID Team


SDK-V0.4.5-RELEASE-NOTES.md

Release notes not available


SDK-V0.4.4-RELEASE-NOTES.md

Release notes not available


SDK-V0.4.3-RELEASE-NOTES.md

Release notes not available


SDK-V0.4.2-RELEASE-NOTES.md

Release notes not available


ZewstID SDK v0.4.1 Release Notes

Release Date: December 7, 2025
Package:

@zewstid/nextjs@0.4.1

Registry: https://npm.zewstid.com/@zewstid/nextjs
Type: Bug Fix Release


๐Ÿ› Critical Bug Fix

This is a critical bug fix release for users of the account linking feature introduced in v0.4.0.

What Was Fixed

Account Linking Endpoint Path Error

The

getLinkedIdentities()
method in the
AccountLinking
class was using an incorrect API endpoint path:

  • Before (v0.4.0):
    /api/v1/auth/identities
    โŒ
  • After (v0.4.1):
    /api/v1/auth/link/identities
    โœ…

This caused the method to return 404 errors in production, breaking the account linking functionality.

Impact

Who is affected:

  • Users of SDK v0.4.0 who are using the account linking feature
  • Applications calling
    zewstid.accountLinking.getLinkedIdentities()

What was broken:

  • getLinkedIdentities()
    would fail with 404 error
  • User Portal identity management UI would show errors
  • Account linking verification flow would fail

What now works:

  • โœ… All account linking methods work correctly
  • โœ… User Portal displays linked identities properly
  • โœ… Full account linking workflow operational

๐Ÿ“ฆ Installation

Upgrade from v0.4.0

npm install @zewstid/nextjs@0.4.1 --registry https://npm.zewstid.com/

Fresh Install

npm install @zewstid/nextjs@latest --registry https://npm.zewstid.com/

Verify Installation

npm list @zewstid/nextjs # Should show: @zewstid/nextjs@0.4.1

๐Ÿ” Technical Details

Files Changed

SDK Source:

  • sdks/packages/nextjs/src/auth/account-linking.ts
    • Line 161: Updated endpoint from
      /auth/identities
      to
      /auth/link/identities

Package Metadata:

  • sdks/packages/nextjs/package.json
    - Version bumped to 0.4.1
  • sdks/packages/nextjs/CHANGELOG.md
    - Added v0.4.1 entry

API Alignment

All account linking SDK methods now correctly use the

/auth/link/*
API route prefix:

SDK MethodCorrect EndpointStatus
initiate()
POST /auth/link/initiate
โœ… Working
verify()
POST /auth/link/verify
โœ… Working
getLinkedIdentities()
GET /auth/link/identities
โœ… Fixed in v0.4.1
unlinkProvider()
DELETE /auth/link/:provider
โœ… Working

๐Ÿงช Testing

Test the Fix

import { ZewstID } from '@zewstid/nextjs'; const zewstid = new ZewstID({ apiUrl: 'https://api.zewstid.com', clientId: 'your-client-id', }); // This should now work correctly const { identities, email } = await zewstid.accountLinking.getLinkedIdentities(); console.log('Linked identities:', identities); // Example output: ['password', 'google', 'magic_link']

Expected Behavior

Before v0.4.1:

Error: 404 - Endpoint not found

After v0.4.1:

{ identities: ['password', 'google', 'magic_link'], email: 'user@example.com' }

๐Ÿ“Š Package Information

Package Details:

  • Name:
    @zewstid/nextjs
  • Version:
    0.4.1
  • Size: 155.5 KB (tarball), 865.6 KB (unpacked)
  • Files: 30 files (TypeScript declarations, ESM, CJS, source maps)
  • Shasum:
    d6853c615817121a05d799eb1faffd39299b8365

Distribution:

  • CommonJS:
    dist/index.js
  • ES Modules:
    dist/index.mjs
  • TypeScript:
    dist/index.d.ts
    ,
    dist/index.d.mts
  • Source Maps: Included for debugging

๐Ÿ”„ Migration Guide

From v0.4.0 to v0.4.1

No code changes required! This is a drop-in replacement.

  1. Update package:

    npm install @zewstid/nextjs@0.4.1 --registry https://npm.zewstid.com/
  2. Verify the fix:

    npm test # Run your existing tests
  3. No application code changes needed - the fix is internal to the SDK

From v0.3.0 or Earlier

If upgrading from v0.3.0 or earlier, you're getting:

  • โœ… Complete account linking system (added in v0.4.0)
  • โœ… Password reset functionality (added in v0.4.0)
  • โœ… This critical bug fix (added in v0.4.1)

See v0.4.0 release notes for full details.


โš ๏ธ Important Notes

Recommendation

If you're using v0.4.0, please upgrade to v0.4.1 immediately.

The account linking feature will not work correctly in production without this fix.

Breaking Changes

None. This is a backward-compatible bug fix.

Deprecations

None.


๐Ÿ“ Complete Changelog

[0.4.1] - 2025-12-07

Fixed

  • Account Linking Endpoint Path: Fixed
    getLinkedIdentities()
    method to use correct endpoint
    /auth/link/identities
    instead of
    /auth/identities
    • Ensures the method works correctly with deployed API Gateway routes
    • All account linking features now fully operational in production

Changed

  • API Endpoint Alignment: Updated all account linking SDK methods to align with production API routes
    • GET /api/v1/auth/link/identities
      (was incorrectly using
      /api/v1/auth/identities
      )

๐ŸŽฏ What's Next

v0.5.0 (Planned)

Future enhancements under consideration:

  • Enhanced error handling with detailed error codes
  • Retry logic for failed API calls
  • Request/response logging capabilities
  • Performance optimizations
  • Additional TypeScript strict mode improvements

Feedback

We welcome your feedback! Please report issues at:


๐Ÿ”— Resources

Documentation:

Package Registry:

Source Code:


๐Ÿ“ž Support

Need Help?


Published: December 7, 2025 06:20 UTC
Author: ZewstID Engineering Team
License: MIT


ZewstID SDK v0.4.0 - Password Reset Release Notes

Release Date: December 6, 2025 Package:

@zewstid/nextjs@0.4.0
Status: Ready for Testing


๐ŸŽฏ Overview

ZewstID SDK v0.4.0 introduces comprehensive password reset functionality, completing the authentication feature set. This release adds secure password reset flows with enterprise-grade security features including user enumeration prevention, rate limiting, and session management.


๐Ÿ†• New Features

Password Reset Flow

Complete password reset implementation with three new API functions:

1.
forgotPassword()
- Initiate Password Reset

Send a password reset email to users who have forgotten their password.

import { forgotPassword } from '@zewstid/nextjs'; // Basic usage const result = await forgotPassword(config, { email: 'user@example.com', clientId: 'your-client-id', }); // With optional CAPTCHA const result = await forgotPassword(config, { email: 'user@example.com', clientId: 'your-client-id', captchaToken: 'captcha-token-from-user', }); console.log(result.message); // "If an account exists with this email, you will receive password reset instructions"

Features:

  • โœ… User enumeration prevention (always returns success)
  • โœ… Rate limiting (3 requests per hour per email, 10 per hour per IP)
  • โœ… Optional CAPTCHA support
  • โœ… 1-hour token expiration
  • โœ… Cryptographically secure tokens

2.
verifyResetToken()
- Verify Token Validity

Optional endpoint to check if a reset token is valid before showing the password reset form.

import { verifyResetToken } from '@zewstid/nextjs'; const result = await verifyResetToken(config, { token: 'reset-token-from-email', }); if (result.valid) { console.log('Token is valid for:', result.email); // "u***@example.com" console.log('Expires in:', result.expiresIn, 'seconds'); } else { console.error('Token error:', result.error_description); }

Features:

  • โœ… Token validation
  • โœ… Expiration checking
  • โœ… Email masking for privacy
  • โœ… Detailed error messages

3.
resetPassword()
- Complete Password Reset

Reset the user's password with the token from the email.

import { resetPassword, ZewstIDError } from '@zewstid/nextjs'; try { const result = await resetPassword(config, { token: 'reset-token-from-email', newPassword: 'NewSecurePassword123!', }); console.log(result.message); // "Password reset successfully. All sessions have been logged out." // Redirect user to login router.push('/login'); } catch (error) { if (error instanceof ZewstIDError) { switch (error.code) { case 'weak_password': alert('Password must be at least 8 characters'); break; case 'token_expired': alert('Reset link has expired. Please request a new one.'); break; case 'invalid_token': alert('Invalid reset link'); break; case 'token_used': alert('This reset link has already been used'); break; case 'too_many_attempts': alert('Too many failed attempts. Please request a new reset link.'); break; } } }

Features:

  • โœ… Password strength validation (8+ characters)
  • โœ… Automatic session invalidation (logs out all devices)
  • โœ… One-time use tokens
  • โœ… Attempt limiting (3 tries per token)
  • โœ… Comprehensive error handling

๐Ÿ”’ Security Features

1. User Enumeration Prevention

The

forgotPassword()
endpoint always returns success, even if the email doesn't exist in the system. This prevents attackers from discovering valid user accounts.

2. Multi-Layer Rate Limiting

  • Email-based: 3 password reset requests per hour per email address
  • IP-based: 10 password reset requests per hour per IP address

3. Secure Token Storage

  • Tokens are hashed with SHA-256 before storage
  • Plaintext tokens never stored in database
  • 1-hour expiration time
  • One-time use (invalidated after successful reset)

4. Session Management

  • All user sessions are invalidated after password reset
  • Users must re-authenticate on all devices
  • Prevents unauthorized access if account was compromised

5. Timing Attack Prevention

  • Random delay added to responses
  • Prevents attackers from using response times to discover valid accounts

6. Weak Password Protection

  • Minimum 8 characters required
  • Clear error messages for password requirements
  • Additional strength checks can be added server-side

๐Ÿ“ฆ API Changes

New Exports

// Functions export { forgotPassword, verifyResetToken, resetPassword, } from '@zewstid/nextjs'; // Types export type { ForgotPasswordParams, ForgotPasswordResponse, VerifyResetTokenParams, VerifyResetTokenResponse, ResetPasswordParams, ResetPasswordResponse, } from '@zewstid/nextjs';

New API Endpoints

EndpointMethodDescription
/api/v1/auth/forgot-password
POSTInitiate password reset
/api/v1/auth/reset-password/verify
GETVerify reset token
/api/v1/auth/reset-password
POSTComplete password reset

๐ŸŽจ Full Example: Password Reset Form

'use client'; import { useState } from 'react'; import { forgotPassword, resetPassword, ZewstIDError } from '@zewstid/nextjs'; import { useRouter } from 'next/navigation'; const config = { clientId: process.env.NEXT_PUBLIC_ZEWSTID_CLIENT_ID!, apiUrl: 'https://api.zewstid.com', }; export function ForgotPasswordForm() { const [email, setEmail] = useState(''); const [loading, setLoading] = useState(false); const [success, setSuccess] = useState(false); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setLoading(true); try { await forgotPassword(config, { email, clientId: config.clientId, }); setSuccess(true); } catch (error) { if (error instanceof ZewstIDError) { if (error.code === 'rate_limit_exceeded') { alert('Too many requests. Please try again later.'); } else { alert('Failed to send reset email. Please try again.'); } } } finally { setLoading(false); } }; if (success) { return ( <div className="success-message"> <h2>Check Your Email</h2> <p> If an account exists with {email}, you will receive password reset instructions within a few minutes. </p> </div> ); } return ( <form onSubmit={handleSubmit}> <h2>Forgot Password?</h2> <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Enter your email" required /> <button type="submit" disabled={loading}> {loading ? 'Sending...' : 'Send Reset Link'} </button> </form> ); } export function ResetPasswordForm({ token }: { token: string }) { const router = useRouter(); const [password, setPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); const [loading, setLoading] = useState(false); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (password !== confirmPassword) { alert('Passwords do not match'); return; } if (password.length < 8) { alert('Password must be at least 8 characters'); return; } setLoading(true); try { await resetPassword(config, { token, newPassword: password, }); alert('Password reset successfully! Please sign in with your new password.'); router.push('/login'); } catch (error) { if (error instanceof ZewstIDError) { switch (error.code) { case 'weak_password': alert('Password is too weak. Please choose a stronger password.'); break; case 'token_expired': alert('Reset link has expired. Please request a new one.'); router.push('/forgot-password'); break; case 'invalid_token': case 'token_used': alert('Invalid or expired reset link. Please request a new one.'); router.push('/forgot-password'); break; case 'too_many_attempts': alert('Too many failed attempts. Please request a new reset link.'); router.push('/forgot-password'); break; default: alert('Failed to reset password. Please try again.'); } } } finally { setLoading(false); } }; return ( <form onSubmit={handleSubmit}> <h2>Reset Your Password</h2> <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="New password" required /> <input type="password" value={confirmPassword} onChange={(e) => setConfirmPassword(e.target.value)} placeholder="Confirm password" required /> <button type="submit" disabled={loading}> {loading ? 'Resetting...' : 'Reset Password'} </button> </form> ); }

๐Ÿšจ Breaking Changes

None. This release is fully backward compatible with v0.3.0.


๐Ÿ› Bug Fixes

API Gateway

  • Fixed Redis import path in password-reset route (
    ../redis
    โ†’
    ../services/redis
    )
  • Fixed CAPTCHA middleware usage (now properly calls
    verifyCaptcha()
    function)

Keycloak Client

  • Added missing
    getUserSessions()
    method for session management
  • Added missing
    deleteSession()
    method for session invalidation

๐Ÿ“Š Technical Implementation

Backend Changes

New Files:

  • api-gateway/src/routes/password-reset.ts
    - Password reset routes (513 lines)
  • api-gateway/src/services/email.ts
    - Added
    sendPasswordResetEmail()
    function

Modified Files:

  • api-gateway/src/keycloak-client.ts
    :
    • Added
      getUserSessions()
      method
    • Added
      deleteSession()
      method
  • api-gateway/src/index.ts
    :
    • Registered password reset routes
    • Added rate limiting for password reset endpoints

SDK Changes

New Files:

  • sdks/packages/nextjs/src/auth/password-reset.ts
    - Password reset client functions (238 lines)

Modified Files:

  • sdks/packages/nextjs/src/index.ts
    :
    • Exported
      forgotPassword
      ,
      verifyResetToken
      ,
      resetPassword
      functions
    • Exported password reset types
  • sdks/packages/nextjs/package.json
    :
    • Version bumped to 0.4.0
    • Added
      password-reset
      and
      forgot-password
      keywords

๐Ÿ“ˆ Performance & Scalability

  • Token Storage: Redis-based with automatic expiration (1 hour)
  • Rate Limiting: Distributed rate limiting using Redis sorted sets
  • Email Delivery: Async email sending via Resend API
  • Session Management: Batch session invalidation for performance

๐Ÿงช Testing

Manual Testing Checklist

  • Request password reset for existing user
  • Verify email is received
  • Click reset link and verify token is valid
  • Reset password successfully
  • Verify all sessions are logged out
  • Test rate limiting (try 4+ requests)
  • Test weak password rejection
  • Test expired token (wait >1 hour)
  • Test token reuse (use same link twice)
  • Test user enumeration protection (try non-existent email)

Automated Testing

Run the comprehensive test suite:

# From /opt/zewst-sso bash /tmp/test-password-reset-simple.sh

๐Ÿ”ฎ Future Enhancements

Potential improvements for v0.5.0:

  1. Password Strength Meter

    • Visual feedback for password strength
    • Configurable password policies
  2. Password History

    • Prevent reusing last N passwords
    • Configurable history length
  3. Email Templates

    • Customizable reset email templates
    • Multi-language support
  4. Advanced Security

    • Suspicious activity detection
    • Geolocation-based alerts
    • Device fingerprinting

๐Ÿ“ Migration Guide

From v0.3.0 to v0.4.0

No breaking changes! Simply update your package:

npm install @zewstid/nextjs@0.4.0 --registry https://npm.zewstid.com/

Add password reset routes to your app:

// app/forgot-password/page.tsx import { ForgotPasswordForm } from '@/components/ForgotPasswordForm'; export default function ForgotPasswordPage() { return <ForgotPasswordForm />; } // app/reset-password/page.tsx import { ResetPasswordForm } from '@/components/ResetPasswordForm'; export default function ResetPasswordPage({ searchParams }: { searchParams: { token: string } }) { return <ResetPasswordForm token={searchParams.token} />; }

๐Ÿ™ Acknowledgments

Special thanks to the ZewstID team for implementing this critical security feature!


๐Ÿ“š Documentation


๐Ÿ› Known Issues

  1. Rate Limiter Counter: Pre-existing issue with counter decrement logic (non-blocking, baseline protection in place)

๐Ÿ“ž Support


Full Changelog: v0.3.0...v0.4.0


ZewstID SDK v0.3.0 Release Notes

Release Date: December 6, 2025 Package:

@zewstid/nextjs
Version: 0.3.0 Status: โœ… Ready for Production


๐ŸŽ‰ What's New

Account Linking System

v0.3.0 introduces comprehensive account linking, allowing users to connect multiple authentication methods (password, OAuth, magic links, OTP, WebAuthn) to a single ZewstID account. This brings ZewstID to feature parity with Auth0, Clerk, and Supabase.

Key Benefits:

  • โœ… No Duplicate Accounts: One user identity across all authentication methods
  • โœ… Consistent
    sub
    (User ID)
    : Same user ID returned regardless of login method
  • โœ… Seamless User Experience: Users can add/remove auth methods without losing their account
  • โœ… Secure Ownership Verification: Password or email code verification required before linking
  • โœ… Self-Service Management: Users manage their own linked identities in the User Portal

๐Ÿ“ฆ Installation

New Installation

npm install @zewstid/nextjs@0.3.0 --registry https://npm.zewstid.com/

Upgrading from v0.2.x

npm update @zewstid/nextjs --registry https://npm.zewstid.com/

No breaking changes! v0.3.0 is 100% backward compatible with v0.2.x.


๐Ÿ”‘ New Features

1. Account Linking SDK Methods

New

AccountLinking
class available via
zewstid.accountLinking
:

import { createZewstID } from '@zewstid/nextjs'; const zewstid = createZewstID({ clientId: process.env.NEXT_PUBLIC_ZEWSTID_CLIENT_ID!, }); // Initiate account linking const { linkingToken, verificationRequired, existingIdentities } = await zewstid.accountLinking.initiate({ email: 'user@example.com', newProvider: 'google', // or 'magic_link', 'otp', 'github', etc. }); // Verify with password if (verificationRequired === 'password') { await zewstid.accountLinking.verify({ linkingToken, password: 'user-password', }); } // Or verify with email code if (verificationRequired === 'email_code') { await zewstid.accountLinking.verify({ linkingToken, code: '123456', // Code sent to user's email }); } // Get all linked identities (requires authentication) const { identities, email } = await zewstid.accountLinking.getLinkedIdentities(); // Returns: ['password', 'google', 'magic_link'] // Check if specific provider is linked const hasGoogle = await zewstid.accountLinking.isLinked('google'); // Unlink a provider (requires at least one other method) await zewstid.accountLinking.unlink('magic_link');

2. Convenience Methods

Link with password verification:

await zewstid.accountLinking.linkWithPassword( 'user@example.com', 'google', 'user-password' );

Link with email code verification:

await zewstid.accountLinking.linkWithCode( 'user@example.com', 'magic_link', '123456' // Code from email );

3. Automatic Conflict Detection

When users try to sign in with a new auth method using an existing email:

// User has account with password, tries to use magic link const response = await fetch('/api/v1/auth/magic-link/send', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: 'user@example.com', createIfNotExists: true, }), }); if (response.status === 409) { // Account conflict detected! const data = await response.json(); console.log(data); // { // error: 'account_exists', // message: 'Account exists with different auth method', // linkingRequired: true, // linkingEndpoint: '/api/v1/auth/link/initiate', // existingIdentities: ['password'] // } // Now initiate account linking const result = await zewstid.accountLinking.initiate({ email: 'user@example.com', newProvider: 'magic_link', }); }

4. New API Endpoints

All endpoints use the

/api/v1/auth/link
base path:

MethodEndpointAuth RequiredDescription
POST
/initiate
NoStart account linking process
POST
/verify
NoVerify ownership and complete linking
GET
/identities
YesGet all linked authentication methods
DELETE
/:provider
YesUnlink an authentication method

Full endpoint paths:

  • POST /api/v1/auth/link/initiate
  • POST /api/v1/auth/link/verify
  • GET /api/v1/auth/link/identities
  • DELETE /api/v1/auth/link/:provider

5. New TypeScript Types

// Account linking options interface LinkAccountOptions { email: string; newProvider: string; clientId?: string; providerData?: { userId: string; username?: string; }; } // Verify linking options interface VerifyLinkingOptions { linkingToken: string; password?: string; code?: string; } // Responses interface LinkingInitiateResponse { linkingToken: string; verificationRequired: 'password' | 'email_code'; existingIdentities: string[]; message: string; } interface LinkingVerifyResponse { success: boolean; userId: string; linkedProvider: string; message: string; } interface LinkedIdentitiesResponse { identities: string[]; email: string; } interface UnlinkResponse { success: boolean; unlinkedProvider: string; remainingIdentities: string[]; }

๐Ÿ” Security Features

Ownership Verification

Users must prove they own the account before linking:

  • Password Verification: If account has password, user must enter it
  • Email Code Verification: If no password, 6-digit code sent to email

Rate Limiting

Multi-layer protection against abuse:

  • IP-based: 10 requests per 15 minutes per IP
  • Email-based: 5 linking attempts per hour per email

Token Security

  • Short-Lived Tokens: Linking tokens expire in 10 minutes
  • Single-Use: Tokens can only be used once
  • Stored in Redis: Tokens invalidated after use or expiration

Safety Guardrails

  • Cannot Unlink Last Method: Users must always have โ‰ฅ1 auth method
  • Audit Logging: All linking/unlinking events logged for security monitoring
  • Email Notifications (optional): Alert users when new methods are added

๐Ÿ’ป User Portal Integration

New Account Linking Page

Beautiful UI at

/link-account
for users to complete account linking:

  • Shows account information and existing authentication methods
  • Password verification form
  • Email code verification form
  • Clear security messaging
  • Success confirmation with auto-redirect

Updated Security Settings

User Portal Security page now shows:

  • All linked authentication methods with visual indicators
  • "Add Another Sign-In Method" button
  • Ability to unlink methods (with safety checks)
  • Clear warnings when attempting to unlink last method

๐Ÿ“š Documentation

Developer Portal

Comprehensive guide available at:

https://developers.zewstid.com/docs/guides/account-linking

Includes:

  • Overview and key benefits
  • How account linking works
  • SDK usage examples
  • API reference
  • Client application integration guide
  • Database schema recommendations
  • Best practices
  • Migration strategies

Code Examples

Check the

/docs/guides/account-linking
page for:

  • โœ… Complete integration examples
  • โœ… Error handling patterns
  • โœ… Database schema recommendations
  • โœ… Best practices (Do's and Don'ts)
  • โœ… Migration guide for existing users

๐Ÿš€ Migration Guide

From v0.2.1 to v0.3.0

โœ… No code changes required! Account linking is an additive feature.

What Changed

  1. New SDK Methods:
    zewstid.accountLinking
    property added
  2. New API Endpoints: 4 new account linking endpoints
  3. Enhanced Conflict Detection: Magic link/OTP return 409 on email conflicts
  4. Extended Keycloak Client: 7 new methods for identity management

Recommended Database Schema

Store the ZewstID

sub
(user ID) in your database instead of email:

-- Add zewstid_sub column to your users table ALTER TABLE users ADD COLUMN zewstid_sub VARCHAR(255) UNIQUE; -- Create index for fast lookups CREATE INDEX idx_users_zewstid_sub ON users(zewstid_sub); -- Update existing users (one-time migration) -- This ensures you always look up by sub, not email UPDATE users u SET zewstid_sub = ( SELECT sub FROM keycloak_users k WHERE k.email = u.email ) WHERE zewstid_sub IS NULL;

Updated Authentication Flow

// Before (v0.2.x): Look up by email const user = await db.users.findOne({ email: session.user.email }); // After (v0.3.0): Look up by sub (recommended) const user = await db.users.findOne({ zewstid_sub: session.user.sub });

Why this matters:

  • โœ… Same user record regardless of auth method used
  • โœ… No duplicate accounts when users link providers
  • โœ… Consistent user identity across your app

Handling Account Conflicts (Optional)

If you want to handle account conflicts in your app:

// In your authentication handler async function handleAuth(email: string, method: string) { try { // Try to send magic link await zewstid.magicLink.send(email); } catch (error) { if (error.status === 409 && error.linkingRequired) { // Account exists with different method // Redirect user to account linking flow return { redirect: `/link-account?email=${email}&provider=${method}`, }; } throw error; } }

๐Ÿงช Testing

Manual Testing Checklist

  • User with password can add Google OAuth
  • User with Google can add password
  • User with magic link can add OTP
  • Password verification works correctly
  • Email code verification works correctly
  • Incorrect password/code is rejected
  • Same
    sub
    returned after linking
  • Client app receives consistent user ID
  • Unlinking works (when > 1 method exists)
  • Cannot unlink last method
  • Rate limiting prevents abuse
  • Audit logs show linking events

Integration Testing

// Example test suite describe('Account Linking', () => { it('should link magic link to password account', async () => { // 1. Create user with password await zewstid.signUpWithPassword('test@example.com', 'Test123!'); // 2. Try to send magic link const response = await fetch('/api/v1/auth/magic-link/send', { method: 'POST', body: JSON.stringify({ email: 'test@example.com' }), }); // 3. Should get 409 conflict expect(response.status).toBe(409); // 4. Initiate linking const result = await zewstid.accountLinking.initiate({ email: 'test@example.com', newProvider: 'magic_link', }); // 5. Verify with password await zewstid.accountLinking.verify({ linkingToken: result.linkingToken, password: 'Test123!', }); // 6. Check linked identities const { identities } = await zewstid.accountLinking.getLinkedIdentities(); expect(identities).toContain('password'); expect(identities).toContain('magic_link'); }); });

๐ŸŽฏ Supported Authentication Methods

All 11 authentication methods support account linking:

MethodLinking SupportVerification Method
Passwordโœ… YesCan be used for verification
Google OAuthโœ… YesFederated identity via Keycloak
GitHub OAuthโœ… YesFederated identity via Keycloak
Microsoft OAuthโœ… YesFederated identity via Keycloak
Apple OAuthโœ… YesFederated identity via Keycloak
Facebook OAuthโœ… YesFederated identity via Keycloak
Magic Linkโœ… YesMarked in user attributes
OTP (Email)โœ… YesMarked in user attributes
OTP (SMS)โœ… YesMarked in user attributes
WebAuthn/Passkeysโœ… YesMarked in user attributes
TOTP/MFAโœ… YesSecondary authentication factor

๐Ÿ“Š Bundle Size

SDK remains lightweight with account linking:

  • Full package: ~24 KB gzipped (all 11 auth methods + account linking)
  • Tree-shakeable: Unused methods automatically removed
  • OAuth-only apps: Still ~8-10 KB gzipped

๐Ÿ”„ What's Next

Future Enhancements (v0.4.0+)

  • CAPTCHA Integration: Add CAPTCHA for account linking verification endpoints
  • Enhanced Notifications: Customizable email templates for linking events
  • Admin Dashboard: Manage user identities via Admin Portal
  • Advanced Analytics: Track account linking metrics
  • Rate Limiter Fix: Debug and fix counter decrement bug

๐Ÿ› Known Issues

  1. Rate Limiter Counter Bug
    • Headers present but counter doesn't decrement properly
    • Redis sorted set logic needs debugging
    • Non-blocking: IP-based rate limiter provides baseline protection
    • Will be fixed in future version

๐Ÿ“ž Support

Having issues?


๐Ÿ™ Acknowledgments

Account linking feature was developed in response to customer feedback and brings ZewstID to feature parity with major authentication providers.

Special thanks to the Zeye team for discovering the duplicate account issue that prompted this feature.


๐Ÿ“ Changelog Summary

Added

  • Account linking system with 7 new SDK methods
  • 4 new API endpoints for account linking
  • Automatic account conflict detection
  • User Portal UI for managing linked identities
  • Developer Portal documentation
  • 6 new TypeScript types
  • Security features (verification, rate limiting, audit logging)

Changed

  • Magic Link/OTP endpoints return 409 on account conflicts
  • Keycloak client extended with 7 new methods
  • OAuth flow redirects to linking page on conflicts

Enhanced

  • User experience: seamless auth method migration
  • Developer experience: automatic conflict detection
  • Security: ownership verification required

Compatibility

  • 100% backward compatible with v0.2.x
  • No breaking changes
  • Additive feature

Published: December 6, 2025 Next Release: v0.4.0 (CAPTCHA integration and enhanced security) Feedback: https://github.com/zewst/zewstid-js/discussions


ZewstID: Complete, production-ready authentication for modern applications.


๐ŸŽ‰ ZewstID SDK v0.2.1 - Released!

Published: December 6, 2025 Registry: https://npm.zewstid.com/@zewstid/nextjs Version: 0.2.1


๐Ÿ“ฆ Installation

npm install @zewstid/nextjs@0.2.1 --registry https://npm.zewstid.com/

Or configure

.npmrc
:

@zewstid:registry=https://npm.zewstid.com/

Then:

npm install @zewstid/nextjs@0.2.1

โœจ What's New in v0.2.1

๐Ÿ”’ Critical Security Fixes

1.
createIfNotExists
Parameter NOW WORKS

The Problem:

  • SDK exposed
    createIfNotExists
    option for magic links and OTP
  • API Gateway completely ignored this parameter
  • Developers thought they had control, but they didn't

The Fix:

  • โœ… API Gateway now respects the parameter
  • โœ… Default:
    true
    (auto-create for convenience)
  • โœ… Security option: Set to
    false
    to only send to existing users

Usage:

// Default behavior - auto-creates users (convenient) await sendMagicLink('user@example.com'); // Security-conscious apps - only existing users await sendMagicLink('user@example.com', { createIfNotExists: false // Returns 404 if user doesn't exist });

2. Email Verification Consistency

The Problem:

  • Password users were marked as
    emailVerified: true
    immediately
  • This bypassed email verification security

The Fix:

  • โœ… ALL users now start with
    emailVerified: false
  • โœ… Verification emails sent automatically
  • โœ… Consistent security across all authentication methods

Best Practice:

// Protect routes with email verification if (!user.emailVerified) { return redirect('/verify-email'); }

3. Per-Email Rate Limiting

Added:

  • โœ… Email-based rate limiting: 5 requests per hour per email
  • โœ… IP-based rate limiting: 10 requests per 15 minutes (existing)
  • โœ… Prevents email bombing and abuse

๐ŸŽฏ Industry Alignment

ZewstID now matches or exceeds industry leaders:

FeatureSupabaseAuth0ClerkZewstID v0.2.1
Auto-create defaultโœ…โœ…โœ…โœ… Yes
Developer controlโœ…โŒโŒโœ… Yes
Email verificationโœ…โœ…โœ…โœ… Always
Rate limitingโœ…โœ…โœ…โœ… Multi-layer

๐Ÿ“š Complete Feature Set

Authentication Methods (11 total)

  1. OAuth/OIDC - Full OpenID Connect
  2. Email/Password - Traditional authentication
  3. Magic Links - Passwordless email (with auto-create control)
  4. OTP - One-time passwords via email/SMS (with auto-create control)
  5. WebAuthn/Passkeys - Biometric authentication
  6. MFA/TOTP - Multi-factor authentication 7-11. Social Login - Google, GitHub, Microsoft, Apple, Facebook

Security Features

  • โœ… Secure token storage (httpOnly cookies)
  • โœ… CSRF protection
  • โœ… PKCE for OAuth flows
  • โœ… Email verification required
  • โœ… Multi-layer rate limiting (IP + Email)
  • โœ… User creation control (
    createIfNotExists
    )

๐Ÿ”„ Migration from v0.1.9

No code changes required! v0.2.1 is 100% backward compatible.

Your existing code:

const { user, signIn, signOut } = useZewstID();

Continues to work unchanged in v0.2.1.

New Capabilities

You can now control user creation:

const { sendMagicLink } = useZewstID({ clientId: process.env.NEXT_PUBLIC_ZEWSTID_CLIENT_ID!, }); // For security-conscious apps: await sendMagicLink('user@example.com', { createIfNotExists: false // NEW - actually works now! });

๐Ÿ“Š Bundle Size

PackageUncompressedGzipped
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 11 authentication methods!


โœ… Testing

All critical features tested and verified:

  • โœ…
    createIfNotExists=true
    auto-creates users
  • โœ…
    createIfNotExists=false
    returns 404 for new users
  • โœ…
    createIfNotExists=false
    works for existing users
  • โœ… OTP default auto-creation behavior
  • โœ… Email verification set to
    false
    for all registrations
  • โœ… Rate limiter middleware applied to endpoints
  • โœ… SDK builds successfully
  • โœ… Zero breaking changes

๐Ÿ“– Documentation

In the Package

When developers install

@zewstid/nextjs@0.2.1
, they automatically receive:

  • โœ… README.md (580 lines) - Complete usage guide
  • โœ… CHANGELOG.md - Version history and migration guide
  • โœ… TypeScript Definitions - Full IntelliSense support
  • โœ… LICENSE - MIT

Online Resources


๐Ÿš€ What's Next

For Developers Using the SDK

  1. Update to v0.2.1:

    npm install @zewstid/nextjs@0.2.1 --registry https://npm.zewstid.com/
  2. Review Security Options:

    • Decide if you want auto-creation (default) or require explicit registration
    • Implement email verification checks in your protected routes
  3. Test Your Application:

    • Verify magic link and OTP flows work as expected
    • Test both auto-creation and explicit registration modes

For the ZewstID Team

Completed โœ…:

  • SDK v0.2.1 implementation
  • Security improvements
  • Documentation updates
  • Git commit and push
  • npm package published

Known Issues:

  • โš ๏ธ Rate limiter counter bug (pre-existing) - Headers present but counter doesn't decrement
    • Recommend separate ticket to debug Redis sorted set logic
    • Non-blocking issue - IP-based rate limiter provides baseline protection

Future Enhancements (v0.3.0):

  • CAPTCHA integration for passwordless methods
  • Fix rate limiter counter bug
  • Enhanced bot detection

๐ŸŽŠ Summary

ZewstID SDK v0.2.1 successfully delivers:

  1. โœ… Fixed Critical Bug -
    createIfNotExists
    now works
  2. โœ… Enhanced Security - Proper email verification for all users
  3. โœ… Industry-Standard Defaults - Matches Supabase/Auth0/Clerk
  4. โœ… Developer Control - Choice between convenience and security
  5. โœ… Zero Breaking Changes - 100% backward compatible
  6. โœ… Comprehensive Documentation - Ready to share with developers

The SDK is production-ready and published! ๐Ÿš€


๐Ÿ“ž Contact


Made with โค๏ธ by the Zewst Team

Was this page helpful?

Let us know how we can improve our documentation