Changelog
All notable changes to next-api-layer are documented here.
v0.4.0 - July 16, 2026#
A token refresh & session-lifecycle release for backends that rotate jti
server-side and detect refresh-token reuse (RFC 9700). Every feature is opt-in
and backward compatible — with no new configuration, behaviour is identical to
0.3.0.
Added#
- Concurrent refresh single-flight (
refresh.singleFlight, defaulttrue): Parallel requests (navigation + SWR polling + RSC fetches) that hit an expired token are coalesced into a singleauth/refreshcall; the rest await its result. This eliminates the orphan/bounce that server-sidejtirotation causes under concurrency. Guaranteed within a single runtime instance. Setfalseto disable. - Refresh-failure reasons +
refresh.onRefreshFailhook: A failed refresh is classified as'expired' | 'revoked' | 'reuse' | 'network' | 'unknown'(RefreshResult.reason), andrefresh.onRefreshFail(reason, req)fires for security telemetry / client notifications. Defaults:409or a body{ code: 'token_reuse' }→reuse;401→expired;403→revoked; transport error →network. Configurable viarefresh.reuseStatusCodes,refresh.reuseCodes, and a customrefresh.classifyFail. - Reuse-aware fail-closed (RFC 9700): A detected token reuse never
silently downgrades to a guest session — it clears all auth cookies and
redirects to login (or
401for API routes) and emits anauth:reuseaudit event. A newauth:refresh:failaudit event is emitted for other failures. access.guestFallbackOnUserRefreshFail(defaulttrue): Whenfalse, a failed user refresh never downgrades to a guest token on any route (always a terminal login /401). The fail-closed switch for admin panels.- Proactive (pre-expiry) refresh (
refresh.proactive,refresh.proactiveWindow): Optionally renew a still-valid token when it is withinproactiveWindowseconds (default120) of expiry instead of waiting for a401. Best-effort: on failure the still-valid token keeps working (a detected reuse stays terminal). - Local JWT validation (
validate.mode: 'local'): Verify the token's signature +expin the proxy instead of callingauth/meevery request, restoring stateless auth. Built-in HMAC (HS256/HS384/HS512) via the Web Crypto API (no dependencies) withvalidate.secret/validate.algorithms; supplyvalidate.verifyfor RS256 / JWKS (e.g.jose). Optionalvalidate.revalidateIntervalre-checks the backend every N seconds (revocation / allowlist). Defaults tomode: 'backend'(unchanged). - Dual-token mode (OAuth2 access + refresh): Set
cookies.refresh(and optionalcookies.refreshOptions, e.g.path: '/api/auth/refresh') to run a short-lived access token plus a separate, long-lived refresh token that is only sent to the refresh endpoint and rotates on use (parseNewRefreshTokenmapper). - New exported types:
RefreshConfig,ValidateConfig,RefreshFailReason,RefreshFailContext.
Notes#
- No breaking changes.
getTokenInfo/refreshTokenkeep their existing signatures; all new behaviour is gated behind new opt-in config. - Single-flight and local verification act per runtime instance; across separate serverless/edge isolates, concurrent requests may still trigger independent refreshes — pair with idempotent refresh handling on the backend for full coverage.
v0.3.0 - June 17, 2026#
A security-hardening release that closes vulnerabilities across the CSRF layer, the middleware header pipeline, the response sanitizer, the rate limiter, and the proxy error path. Read the Breaking Changes before upgrading.
Security#
- CSRF tokens are now cryptographically verified (token forgery fixed): The
double-submit strategy previously accepted any request whose cookie and
header values matched, so an attacker able to plant a cookie on a sibling
subdomain could forge a valid pair. Tokens are now HMAC-SHA256 signed
(
<hmac>.<randomValue>) and the signature is verified on every state-changing request. Forged or tampered tokens are rejected withinvalid-token-signature. - Constant-time CSRF comparisons: Cookie/header equality and HMAC verification both use a constant-time comparison to remove timing side-channels.
- Insecure randomness fallback removed (fail-closed): CSRF token and secret
generation now require the Web Crypto API and throw if it is unavailable,
instead of silently falling back to
Math.random(). An insecure token can never be issued. - Spoofed internal headers stripped (auth impersonation fixed): Inbound
requests carrying
x-auth-user,x-refreshed-token, orx-localecould previously impersonate an authenticated user or override the resolved locale. These internal headers are now stripped from every incoming request before the proxy sets its own verified values — on all paths (pass-through, refresh, valid-token, excluded, auth-API) — and before the upstream backend fetch. - i18n middleware can no longer bypass an auth redirect (redirect-precedence):
When the auth layer produced a terminal response (e.g. a redirect to
/loginfor a protected route, or a401JSON), a subsequent i18nnext()/rewrite()could discard it and serve the protected page. Terminal auth responses now win over i18n forwards; only the i18n cookies (e.g.NEXT_LOCALE) are carried over. - Allow-list sanitizer XSS bypass fixed: In
mode: 'allowList', dangerous attributes on otherwise-allowed tags (inline event handlers likeonerror,style, andjavascript:/data:URLs inhref/src) were passed through. Attributes on allow-listed tags are now sanitized, anddata:/ dangerous-URL detection was broadened. - Rate-limit prefetch bypass narrowed:
skipPrefetchpreviously skipped the limiter for any method carrying a prefetch header, so an attacker could evade rate limiting onPOST(etc.) by addingNext-Router-Prefetch. Prefetch skipping is now restricted to safe (GET/HEAD) methods. - Proxy error responses no longer leak internals: The
502path no longer returnserror.messageto the client; it logs the error server-side ([Proxy Error]) and returns a generic message. - CSRF secret hygiene warning: When CSRF is enabled without an explicit
csrf.secret, the library now logs a warning and uses a per-process ephemeral secret (tokens will not validate across instances/restarts until you set one).
Breaking Changes#
-
createCsrfValidatoris now fully async.validateRequest(),generateToken(), andattachCsrfCookie()return promises. If you call the validator directly, you mustawaitthe results:DIFFLoading...Using
createAuthProxyrequires no change — it already awaits internally. -
CSRF token format changed to
<hmac>.<randomValue>. Tokens minted by0.2.xare not valid in0.3.0and vice-versa. Deploy all instances together; clients receive a fresh token automatically on their next safe request, so no user action is required. -
Web Crypto is now required for CSRF.
crypto.subtleandcrypto.getRandomValuesmust be available (Node.js 18+ or any Edge runtime). The library throws a clear error instead of degrading to insecure randomness. -
Set
csrf.secretexplicitly in production. Without it, tokens are signed with a per-process random secret and will fail to validate across multiple instances or after a restart.
Migration#
No code changes are required if you use createAuthProxy — it awaits the CSRF
validator internally. If you call createCsrfValidator directly, add await to
validateRequest(), generateToken(), and attachCsrfCookie(). In production,
set a stable csrf.secret (e.g. from an environment variable) so signed tokens
validate across instances and restarts.
v0.2.5 - June 8, 2026#
Fixed#
useAuthredirected during render: WhenuseAuthreceivedredirectTo/redirectIfFound— and thereforeuseRequireAuthanduseRedirectIfAuth— it calledrouter.replace()synchronously in the render body. On React 18/19 this threwCannot update a component (Router) while rendering a different component. Navigation now runs inside auseEffect, firing after commit instead of during render.useRequireAuththrew during render: It calledthrow new Error('Authentication required')in the render phase, which aborted the component before its redirect effect could commit. The throw was removed; the hook now relies on the effect-driven redirect. Guard your UI onisLoading/isAuthenticatedwhile the redirect settles.
v0.2.4 - June 3, 2026#
Fixed#
- Rate limiter double-counting bug:
createAuthProxyinvokedrateLimiter.check()twice per request — once at the gate and again when applying theX-RateLimit-*response headers. Every request was counted twice, effectively halving the configured limit (e.g. a limit of100behaved like50). The check result is now computed once and reused for both gating and header application.
Added#
skipPrefetchoption (RateLimitConfig.skipPrefetch, defaulttrue): Next.js<Link>prefetch requests no longer consume rate-limit tokens. Prefetches are detected vianext-router-prefetch,sec-purpose,purpose/x-purpose, andx-mozheaders. Set tofalseto count them.ipHeadersoption (RateLimitConfig.ipHeaders): Configure the ordered list of headers used to resolve the real client IP for rate-limit keying. Defaults to['cf-connecting-ip', 'true-client-ip', 'x-real-ip', 'x-forwarded-for'], adding first-class Cloudflare / proxy support. Thex-forwarded-forvalue correctly uses the first (client) hop.- New exported helpers:
getClientIp(req, headerPriority?)— resolve the real client IP behind proxies and CDNs.isPrefetchRequest(req)— detect Next.js prefetch requests.DEFAULT_IP_HEADERS— the default IP header priority list.
Notes#
- The built-in rate limiter remains in-memory (per-instance). For multi-instance / serverless deployments that need a shared counter, a pluggable Redis-backed store is planned for a future release (it requires an async store API and is intentionally deferred to avoid a breaking change).
Migration#
No code changes required. The double-count fix means your real effective limit
now matches the configured value — if you previously compensated by doubling
your max, you can restore it to the intended number.
v0.2.3 - April 20, 2026#
Fixed#
- Sanitization over-escaping bug: Plain text characters (
/,',`,=) were being HTML-escaped to entities like/,',=, which corrupted user-facing text in React/Vue/Angular apps (e.g.O'Reilly's Booksappeared asO'Reilly's Books). Modern frameworks already escape text at render time, so API-level entity escaping is redundant and harmful.
Changed#
- Default
modeis now'strip'(was'escape'). Removes HTML tags while preserving plain text characters. Matches the philosophy of DOMPurify and sanitize-html: operate at the tag level, not the character level. escapemode char set minimized to the OWASP minimum: only<,>,&,"are escaped. Apostrophes, slashes, backticks, and equals signs pass through unchanged (safe in text content; double-quoted HTML attributes make'safe too).stripmode now only removes well-formed tags (<letter...>/</letter...>). Bare<,>,&characters in text like5 < 10 and 20 > 3are preserved.SanitizationConfig.modeadded as a public, documented option:'strip' | 'escape' | 'allowList'.
Migration#
No code changes required for most apps — the new defaults produce safer, cleaner
output. If you explicitly set mode: 'escape' and relied on the old aggressive
char set, tag injection (<script>, <img onerror>, etc.) is still blocked,
but /, ', `, = now pass through. This is the correct behavior for
data bound to React/Vue/Angular text nodes.
Before / After#
v0.2.2 - April 17, 2026#
Added#
-
Enhanced Debug Body Logging: Smart request/response body logging with DX-focused features
logRequestBody- Log request payloads separately (default: false)logResponseBody- Log response data separately (default: false)maxStringLength- Per-field string truncation (default: 500)maxArrayItems- Array item limit with "... and N more" indicator (default: 10)maxDepth- Object nesting depth limit (default: 5)autoMask- Enable/disable sensitive field masking (default: true)sensitiveFields- Customize which fields to mask (password, token, secret, cvv, etc.)
-
Smart Binary Detection: Automatically detects and summarizes binary-like data
- Base64 strings shown as
[base64, 45KB] - Data URLs shown as
[data URL (image/png), 2.3MB] - Prevents terminal spam from large payloads
- Base64 strings shown as
-
Enhanced FormData Logging: Better visibility for file uploads
- File info:
[File: photo.jpg, 2.4MB, image/jpeg] - Sensitive fields masked in FormData
- Field count with "... and N more fields" indicator
- File info:
Changed#
logBodydeprecated in favor oflogRequestBody/logResponseBody(still works for backward compatibility)bodyPreviewLengthdeprecated in favor ofmaxStringLength(still works for backward compatibility)
Example#
v0.2.1 - April 16, 2026#
Fixed#
- URL field sanitization: URL values in API payloads are now preserved correctly
- Previously:
https://example.com→https//example.com - Now: URLs with safe protocols (https, http, mailto, tel, ftp) are preserved
- Relative paths (
/callback) are also preserved - XSS vectors (
javascript:,data:) are still sanitized
- Previously:
Improved#
- Documentation sync: Fixed mismatches between docs and TypeScript types
errorMessageskeys corrected:connectionError,serverError,timeout,noTokenmethodSpoofingobject config withfieldNameoption documentedswrConfigfullSWRConfigurationsupport noted
v0.2.0 - April 15, 2026#
Added#
-
Typed Error Classes: New error hierarchy for better error handling
ApiError- Base error class with code and timestampHttpError- HTTP errors with status code and statusTextTimeoutError- Request timeout errors with endpoint and timeout infoNetworkError- Network failures with cause trackingAuthError- Authentication errors with reason (no_token, invalid_token, expired_token)ValidationError- Validation errors with field-level error messagesRateLimitError- Rate limit errors with retryAfter, limit, remaining info- Type guards:
isApiError(),isHttpError(),isTimeoutError(),isNetworkError(),isAuthError(),isRateLimitError() - Utilities:
isRetryableStatus(),isRetryableError()
-
Retry Logic: Automatic retry for failed requests
createApiClient({ retry: { enabled: true, maxAttempts: 3 } })- Backoff strategies:
'exponential','linear','fixed' - Configurable retry conditions: status codes, network errors
- Per-request override:
api.get('/endpoint', { retry: false })
-
Debug Mode: Development logging for requests/responses
createApiClient({ debug: { enabled: true } })- Log requests, responses, timing, headers, body preview
- Custom logger support
-
Request Deduplication: Prevent duplicate in-flight requests
createApiClient({ dedupe: { enabled: true, methods: ['GET'] } })- Automatically returns same promise for identical concurrent requests
-
Request ID / Correlation: Request tracing headers
createApiClient({ requestId: { enabled: true } })- Custom header name and generator support
-
Next.js 16+ Support: Documentation and examples now support both:
proxy.tswithexport const proxy = authProxy(Next.js 16+)middleware.tswithexport default authProxy(Next.js 14-15)
Changed#
- BREAKING: methodSpoofing config: Now accepts
boolean | MethodSpoofingConfig - CLI: Uses native Node.js
readline/promises- zero runtime dependencies - CLI: New questions for protected routes and auth routes
Removed#
- prompts dependency: CLI now uses native Node.js APIs
Migration from v0.1.x#
- If using methodSpoofing as object - no changes needed
- CLI - regenerate config with
npx next-api-layer init(optional)
v0.1.11 - April 13, 2026#
Fixed#
- Non-ASCII characters in x-auth-user header: User data with Turkish (ğ, ş, ı, ö, ü, ç), German (ä, ö, ü, ß), or other non-ASCII characters now works correctly
- HTTP headers only support ISO-8859-1 (0-255), characters like
ğ(287) causedTypeError: Cannot convert argument to a ByteString - User data is now Base64 encoded in proxy and decoded in
getServerUser() - This is an internal change - no API changes required
- HTTP headers only support ISO-8859-1 (0-255), characters like
Migration from v0.1.10#
No breaking changes. Simply update your package:
v0.1.10 - April 10, 2026#
Fixed#
- login/register success detection:
login()andregister()functions now correctly returnsuccess: truewhen API responds with success- Previously returned
success: falsewhen response didn't include user data - Now checks
res.ok && json.success !== falseinstead of relying on user data presence - User data is now optional - if not in response, library fetches from
/meendpoint
- Previously returned
Added#
- parseAuthResponse prop: New optional prop for custom login/register response parsing
- Allows handling non-standard backend auth response formats
- Example:
parseAuthResponse={(json) => ({ success: json.ok, user: json.result?.user })}
- AuthResponseParsed type: Exported from
next-api-layer/clientfor TypeScript users
Migration from v0.1.9#
No breaking changes. Simply update your package:
v0.1.9 - April 10, 2026#
Fixed#
- i18n route matching:
protectedRoutes,authRoutes, andpublicRoutesnow correctly work withlocalePrefix: 'always'- Routes like
/tr/loginare now properly matched against config/login - Locale prefix is automatically stripped before route matching
- Works with any locale configured in
i18n.locales
- Routes like
Added#
- New
stripLocale()utility function exported fromnext-api-layer/proxy- Strips locale prefix from pathname for custom route comparisons
Migration from v0.1.8#
No breaking changes. Simply update your package:
v0.1.8 - April 9, 2026#
Fixed#
- x-locale header loss: Fixed issue where
x-localeheader was lost when usingi18n.middlewareoption- Header is now set as response header instead of request header
- Ensures locale is available in server components via
headers().get('x-locale')
Migration from v0.1.7#
No breaking changes. Simply update your package:
v0.1.7 - April 8, 2026#
Added#
- i18n Middleware Integration: New
middlewareoption inI18nConfigfor seamless integration with next-intl and similar libraries- Library calls your i18n middleware internally and merges responses automatically
- Preserves critical headers (
x-locale,x-auth-user,x-refreshed-token) across middleware chain - No more losing locale headers when using custom middleware hooks
Fixed#
- Header preservation:
afterAuthhook no longer loses library-set headers when calling external middleware functions
Example Usage#
Migration from v0.1.6#
No breaking changes. To use the new middleware integration, add the middleware option to your i18n config.
v0.1.6 - April 7, 2026#
Added#
- Per-request sanitization control: New options in
RequestOptionsfor fine-grained sanitization controlskipSanitize: boolean- Skip all sanitization for a specific requestskipSanitizeFields: string[]- Skip sanitization for specific fields only
patch()method now acceptsRequestOptionsparameter (was missing)
Example Usage#
Migration from v0.1.5#
No breaking changes. Simply update your package:
v0.1.5 - April 4, 2026#
Added#
- i18n Support: Automatic locale detection and injection for API requests
- Added
i18nconfig option tocreateAuthProxyfor locale detection from URL path - Added
i18nconfig option tocreateApiClientfor auto-appending?lang={locale}to requests - New
x-localeheader for passing locale from middleware to route handlers - Configurable
paramName(default:lang),locales, anddefaultLocale
- Added
Changed#
HEADERSconstant now includesLOCALE: 'x-locale'- Proxy handlers now extract locale from URL pathname and set
x-localeheader - API client reads
x-localeheader and appends locale query parameter to backend requests
Migration from v0.1.4#
No breaking changes. To enable i18n, add the config:
v0.1.4 - April 1, 2026#
Fixed#
- Empty cookie creation bug: Fixed an issue where
cookies.delete()was called on non-existent cookies, causing empty-value cookies to be set in the browser. Now all delete operations check for cookie existence before attempting deletion.
Added#
- New
safeDeleteCookiehelper function that only deletes cookies that actually exist in the request. - Updated
deleteAllAuthCookiesto acceptreqparameter for cookie existence checking.
Changed#
- All cookie delete operations now verify cookie existence before deletion to prevent phantom cookies.
Migration from v0.1.3#
No breaking changes. Simply update your package:
v0.1.3 - March 20, 2026#
Added#
- Initial stable release with proxy and API client functionality
- Guest token support with automatic creation
- Token validation and refresh mechanisms
- Rate limiting support
- CSRF protection
- Audit logging capabilities
- next-intl integration support
Features#
createAuthProxy- Main proxy function for Next.jscreateApiClient- Server-side API clientcreateProxyHandler- Flexible proxy handler for route handlersuseAuth- Client-side auth hookAuthProvider- React context providergetServerUser- Server-side user helper