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/nextjsnpm install @zewstid/nextjs@0.4.6๐จ Critical Fix: ID Token Storage in getZewstIDCallbacks()
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()src/auth.ts// โ 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:
- Authenticate users to their backend (access tokens may be opaque)
- Perform OIDC logout (requires )
id_token_hint - 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
| Feature | v0.4.5 | v0.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 Case | Impact | Status |
|---|---|---|
Apps using getZewstIDCallbacks() | High | โ Fixed |
| Backend token decoding | High | โ Fixed |
OIDC logout with id_token_hint | Medium | โ Fixed |
Apps using createZewstIDAuth() | None | Already 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()
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.accessTokensession.idTokenBefore (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()
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 Type | Format | Purpose | When to Use |
|---|---|---|---|
| Access Token | Opaque or JWT | API resource access | Calling external APIs (Google, GitHub, etc.) |
| ID Token | Always JWT | User identity claims | Authenticating user in your backend |
| Refresh Token | Opaque | Token refresh | Getting 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 from SDK v0.4.5 or earlier
getZewstIDCallbacks() - 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 (already has ID tokens)
createZewstIDAuth() - Applications not using backend authentication
Breaking Changes
None. This is a pure bugfix with 100% backward compatibility:
- โ All existing code continues to work
- โ
is still available
session.accessToken - โ
Only adds , doesn't remove anything
session.idToken
However, you should update your backend to use
session.idTokensession.accessToken๐ Changelog Summary
Fixed
- Critical: ID token not stored in
getZewstIDCallbacks()- Added in JWT callback
token.idToken = account.id_token; - Added in session callback
session.idToken = token.idToken; - Fixes backend authentication failures (opaque access tokens)
- Fixes improper OIDC logout (missing )
id_token_hint - Aligns behavior with
createZewstIDAuth()
- Added
Changed
- None (pure bugfix release)
Added
- None (pure bugfix release)
Removed
- None (pure bugfix release)
๐ Related Issues
This fix resolves:
- "Cannot decode token" - Access tokens may be opaque
- "Missing user claims" - Access tokens don't have email/name
- "Logout doesn't clear SSO session" - Need for OIDC logout
id_token_hint - "Backend authentication fails" - Wrong token type sent to backend
- "Inconsistent SDK behavior" - vs
getZewstIDCallbacks()createZewstIDAuth()
See TROUBLESHOOTING-TOKEN-TYPE-MISMATCH.md for detailed troubleshooting.
๐ Related Resources
- Troubleshooting: Token Type Mismatch
- Next.js SDK Documentation
- Complete SDK Changelog
- API Reference
- OAuth 2.0 Token Types
- OpenID Connect ID Tokens
๐ Support
If you encounter any issues after upgrading:
- Clear browser cache and cookies
- Log out and log back in to get new session with ID token
- Check the troubleshooting guide: TROUBLESHOOTING-TOKEN-TYPE-MISMATCH.md
- Contact support:
- Email: support@zewstid.com
- Discord: https://discord.gg/zewstid
- GitHub Issues: https://github.com/zewstid/zewstid-js/issues
โ Migration Checklist
- Upgrade to
@zewstid/nextjs@0.4.6 - Update backend to use instead of
session.idTokensession.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.1Registry: 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()AccountLinking- 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:
- would fail with 404 error
getLinkedIdentities() - 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 to
/auth/identities/auth/link/identities
- Line 161: Updated endpoint from
Package Metadata:
- - Version bumped to 0.4.1
sdks/packages/nextjs/package.json - - Added v0.4.1 entry
sdks/packages/nextjs/CHANGELOG.md
API Alignment
All account linking SDK methods now correctly use the
/auth/link/*| SDK Method | Correct Endpoint | Status |
|---|---|---|
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.tsdist/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.
-
Update package:
npm install @zewstid/nextjs@0.4.1 --registry https://npm.zewstid.com/ -
Verify the fix:
npm test # Run your existing tests -
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 method to use correct endpoint
getLinkedIdentities()instead of/auth/link/identities/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
- (was incorrectly using
GET /api/v1/auth/link/identities)/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:
- GitHub: https://github.com/zewstid/zewstid-js/issues
- Email: support@zewstid.com
๐ Resources
Documentation:
- SDK Documentation: https://developers.zewstid.com/docs/sdk/nextjs
- Account Linking Guide: https://developers.zewstid.com/docs/guides/account-linking
- API Reference: https://developers.zewstid.com/docs/api/reference
Package Registry:
- Verdaccio: https://npm.zewstid.com/@zewstid/nextjs
- Version 0.4.1: https://npm.zewstid.com/@zewstid/nextjs/-/nextjs-0.4.1.tgz
Source Code:
- Repository: https://github.com/zewstid/zewstid-js
- Branch:
zewst-id-phase14-sdk - Commit:
b5857a8
๐ Support
Need Help?
- Documentation: https://developers.zewstid.com
- Discord: https://discord.gg/zewstid
- Email: support@zewstid.com
- GitHub Issues: https://github.com/zewstid/zewstid-js/issues
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๐ฏ 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
forgotPassword()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
verifyResetToken()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
resetPassword()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()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
| Endpoint | Method | Description |
|---|---|---|
/api/v1/auth/forgot-password | POST | Initiate password reset |
/api/v1/auth/reset-password/verify | GET | Verify reset token |
/api/v1/auth/reset-password | POST | Complete 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 function)
verifyCaptcha()
Keycloak Client
- Added missing method for session management
getUserSessions() - Added missing method for session invalidation
deleteSession()
๐ Technical Implementation
Backend Changes
New Files:
- - Password reset routes (513 lines)
api-gateway/src/routes/password-reset.ts - - Added
api-gateway/src/services/email.tsfunctionsendPasswordResetEmail()
Modified Files:
- :
api-gateway/src/keycloak-client.ts- Added method
getUserSessions() - Added method
deleteSession()
- Added
- :
api-gateway/src/index.ts- Registered password reset routes
- Added rate limiting for password reset endpoints
SDK Changes
New Files:
- - Password reset client functions (238 lines)
sdks/packages/nextjs/src/auth/password-reset.ts
Modified Files:
- :
sdks/packages/nextjs/src/index.ts- Exported ,
forgotPassword,verifyResetTokenfunctionsresetPassword - Exported password reset types
- Exported
- :
sdks/packages/nextjs/package.json- Version bumped to 0.4.0
- Added and
password-resetkeywordsforgot-password
๐ 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:
-
Password Strength Meter
- Visual feedback for password strength
- Configurable password policies
-
Password History
- Prevent reusing last N passwords
- Configurable history length
-
Email Templates
- Customizable reset email templates
- Multi-language support
-
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
- Rate Limiter Counter: Pre-existing issue with counter decrement logic (non-blocking, baseline protection in place)
๐ Support
- Documentation: https://developers.zewstid.com
- Discord: https://discord.gg/zewstid
- GitHub Issues: https://github.com/zewst/zewstid-js/issues
- Email: support@zewstid.com
Full Changelog: v0.3.0...v0.4.0
ZewstID SDK v0.3.0 Release Notes
Release Date: December 6, 2025 Package:
@zewstid/nextjs๐ 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 (User ID): Same user ID returned regardless of login method
sub - โ 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
AccountLinkingzewstid.accountLinkingimport { 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| Method | Endpoint | Auth Required | Description |
|---|---|---|---|
| POST | /initiate | No | Start account linking process |
| POST | /verify | No | Verify ownership and complete linking |
| GET | /identities | Yes | Get all linked authentication methods |
| DELETE | /:provider | Yes | Unlink an authentication method |
Full endpoint paths:
POST /api/v1/auth/link/initiatePOST /api/v1/auth/link/verifyGET /api/v1/auth/link/identitiesDELETE /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- 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- โ 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
- New SDK Methods: property added
zewstid.accountLinking - New API Endpoints: 4 new account linking endpoints
- Enhanced Conflict Detection: Magic link/OTP return 409 on email conflicts
- Extended Keycloak Client: 7 new methods for identity management
Recommended Database Schema
Store the ZewstID
sub-- 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 returned after linking
sub - 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:
| Method | Linking Support | Verification Method |
|---|---|---|
| Password | โ Yes | Can be used for verification |
| Google OAuth | โ Yes | Federated identity via Keycloak |
| GitHub OAuth | โ Yes | Federated identity via Keycloak |
| Microsoft OAuth | โ Yes | Federated identity via Keycloak |
| Apple OAuth | โ Yes | Federated identity via Keycloak |
| Facebook OAuth | โ Yes | Federated identity via Keycloak |
| Magic Link | โ Yes | Marked in user attributes |
| OTP (Email) | โ Yes | Marked in user attributes |
| OTP (SMS) | โ Yes | Marked in user attributes |
| WebAuthn/Passkeys | โ Yes | Marked in user attributes |
| TOTP/MFA | โ Yes | Secondary 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
Pre-existing Issues (Not Related to Account Linking)
- 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?
- ๐ Documentation: https://developers.zewstid.com/docs/guides/account-linking
- ๐ฌ Discord: https://discord.gg/zewstid
- ๐ง Email: support@zewstid.com
- ๐ Bug Reports: https://github.com/zewst/zewstid-js/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
createIfNotExistsThe Problem:
- SDK exposed option for magic links and OTP
createIfNotExists - API Gateway completely ignored this parameter
- Developers thought they had control, but they didn't
The Fix:
- โ API Gateway now respects the parameter
- โ
Default: (auto-create for convenience)
true - โ
Security option: Set to to only send to existing users
false
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 immediately
emailVerified: true - 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:
| Feature | Supabase | Auth0 | Clerk | ZewstID v0.2.1 |
|---|---|---|---|---|
| Auto-create default | โ | โ | โ | โ Yes |
| Developer control | โ | โ | โ | โ Yes |
| Email verification | โ | โ | โ | โ Always |
| Rate limiting | โ | โ | โ | โ Multi-layer |
๐ Complete Feature Set
Authentication Methods (11 total)
- OAuth/OIDC - Full OpenID Connect
- Email/Password - Traditional authentication
- Magic Links - Passwordless email (with auto-create control)
- OTP - One-time passwords via email/SMS (with auto-create control)
- WebAuthn/Passkeys - Biometric authentication
- 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
| Package | Uncompressed | Gzipped |
|---|---|---|
| Main | 51 KB | ~15 KB |
| Handlers | 4.5 KB | ~2 KB |
| Middleware | 7.6 KB | ~3 KB |
| Server | 11 KB | ~4 KB |
Total: ~24 KB gzipped for full package with all 11 authentication methods!
โ Testing
All critical features tested and verified:
- โ
auto-creates users
createIfNotExists=true - โ
returns 404 for new users
createIfNotExists=false - โ
works for existing users
createIfNotExists=false - โ OTP default auto-creation behavior
- โ
Email verification set to for all registrations
false - โ Rate limiter middleware applied to endpoints
- โ SDK builds successfully
- โ Zero breaking changes
๐ Documentation
In the Package
When developers install
@zewstid/nextjs@0.2.1- โ README.md (580 lines) - Complete usage guide
- โ CHANGELOG.md - Version history and migration guide
- โ TypeScript Definitions - Full IntelliSense support
- โ LICENSE - MIT
Online Resources
- Homepage: https://docs.zewstid.com/sdks/nextjs
- API Reference: https://docs.zewstid.com/sdks/nextjs/api
- GitHub: https://github.com/zewst/zewst-auth
- Support: support@zewstid.com
๐ What's Next
For Developers Using the SDK
-
Update to v0.2.1:
npm install @zewstid/nextjs@0.2.1 --registry https://npm.zewstid.com/ -
Review Security Options:
- Decide if you want auto-creation (default) or require explicit registration
- Implement email verification checks in your protected routes
-
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:
- โ
Fixed Critical Bug - now works
createIfNotExists - โ Enhanced Security - Proper email verification for all users
- โ Industry-Standard Defaults - Matches Supabase/Auth0/Clerk
- โ Developer Control - Choice between convenience and security
- โ Zero Breaking Changes - 100% backward compatible
- โ Comprehensive Documentation - Ready to share with developers
The SDK is production-ready and published! ๐
๐ Contact
- Email: support@zewstid.com
- Discord: https://discord.gg/zewstid
- GitHub Issues: https://github.com/zewstid/zewstid-js/issues
Made with โค๏ธ by the Zewst Team
Was this page helpful?
Let us know how we can improve our documentation