diff --git a/fern/products/sdks/guides/dynamic-authentication.mdx b/fern/products/sdks/guides/dynamic-authentication.mdx
new file mode 100644
index 000000000..2662911d5
--- /dev/null
+++ b/fern/products/sdks/guides/dynamic-authentication.mdx
@@ -0,0 +1,56 @@
+---
+title: Dynamic authentication
+description: Implement dynamic authentication patterns like short-lived JWT signing in your SDKs.
+---
+
+Your API may require dynamic authentication where credentials need to be generated or refreshed for each request, such as signing short-lived JWTs or rotating tokens. Fern-generated SDKs support this pattern through language-specific approaches.
+
+## Language-specific guides
+
+Each language has its own recommended approach for implementing dynamic authentication:
+
+
+
+ Use custom fetcher middleware to inject authentication logic in a single place for all requests. Supports JWT signing, OAuth token refresh, and more.
+
+
+ Use method overrides to inject authentication logic for each API call. Supports JWT signing, OAuth token refresh, and more.
+
+
+
+## Common use cases
+
+Dynamic authentication is useful for several scenarios:
+
+- **Short-lived JWT signing**: Generate and sign JWTs that expire after a short period (e.g., 15 seconds) for enhanced security
+- **OAuth token refresh**: Automatically refresh expired access tokens before each request
+- **HMAC (Hash-based Message Authentication Code) signing**: Sign requests with HMAC signatures based on request content
+- **Rotating API keys**: Switch between multiple API keys based on rate limits or other criteria
+- **Time-based tokens**: Generate tokens that include timestamps or nonces for replay protection
+
+## Important considerations
+
+When implementing dynamic authentication, keep these language-agnostic considerations in mind:
+
+### Security
+
+- **Secure key storage**: Never hardcode private keys or secrets; use environment variables or secure key management systems
+- **Server-side only**: For JWT signing with private keys, ensure this is only done in server-side code, never in browser environments
+- **Avoid double authentication**: If your API already uses bearer token authentication in the Fern definition, be careful not to override existing authentication headers
+
+### Performance
+
+- **Token caching**: Cache tokens to avoid regenerating them on every request, balancing security (shorter token lifetime) with performance (less frequent regeneration)
+- **Grace period**: Refresh tokens slightly before they expire to avoid edge cases where a token expires during request processing
+- **Concurrency**: Be mindful of race conditions when caching tokens in multi-threaded environments
+
+### Time synchronization
+
+- **Clock drift**: Be aware of potential clock drift between your client and server; consider adding tolerance to token expiration checks
+- **Timestamp precision**: Use Unix timestamps (seconds since epoch) for JWT `iat` and `exp` claims to match standards
+
+## See also
+
+- [Adding custom code](/sdks/custom-code) - Learn more about extending generated SDKs
+- [TypeScript custom code](/sdks/generators/typescript/custom-code) - TypeScript-specific customization guide
+- [Python custom code](/sdks/generators/python/custom-code) - Python-specific customization guide
diff --git a/fern/products/sdks/overview/python/dynamic-authentication.mdx b/fern/products/sdks/overview/python/dynamic-authentication.mdx
new file mode 100644
index 000000000..43ce67c78
--- /dev/null
+++ b/fern/products/sdks/overview/python/dynamic-authentication.mdx
@@ -0,0 +1,148 @@
+---
+title: Dynamic authentication
+description: Implement dynamic authentication patterns like short-lived JWT signing in Python SDKs using method overrides.
+---
+
+Your API may require dynamic authentication where credentials need to be generated or refreshed for each request, such as signing short-lived JWTs or rotating tokens. Python SDKs support this pattern through method overrides.
+
+## Method override pattern
+
+For Python SDKs, you can implement dynamic authentication by extending the generated client and overriding methods to inject authentication logic.
+
+### Example: Short-lived JWT signing
+
+Here's how to implement JWT signing for Python SDKs:
+
+
+
+### Create a wrapper client
+
+Create a custom client class that extends the generated Fern client and adds JWT signing logic:
+
+```python title="src/wrapper/client.py"
+from .client import PlantStoreClient as FernClient
+import jwt
+import time
+from typing import Optional, Dict, Any
+
+class PlantStoreClient(FernClient):
+ def __init__(self, *, private_key: str, environment: str):
+ super().__init__(environment=environment)
+ self._private_key = private_key
+
+ def _generate_jwt(self) -> str:
+ """Generate a short-lived JWT token valid for 15 seconds"""
+ now = int(time.time())
+ payload = {
+ "iat": now,
+ "exp": now + 15,
+ }
+ return jwt.encode(payload, self._private_key, algorithm="RS256")
+
+ def _with_jwt(self, request_options: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
+ """Inject JWT into request options"""
+ token = self._generate_jwt()
+ options = request_options or {}
+ headers = options.get("headers", {})
+ headers["Authorization"] = f"Bearer {token}"
+ options["headers"] = headers
+ return options
+
+ def get_plant(self, plant_id: str, *, request_options: Optional[Dict[str, Any]] = None):
+ return super().plants.get(plant_id, request_options=self._with_jwt(request_options))
+
+ def create_plant(self, request: Any, *, request_options: Optional[Dict[str, Any]] = None):
+ return super().plants.create(request, request_options=self._with_jwt(request_options))
+
+ # Override additional methods as needed...
+```
+
+### Export the wrapper client
+
+Update your `__init__.py` to export the wrapper instead of the generated client:
+
+```python title="src/__init__.py"
+from .wrapper.client import PlantStoreClient
+
+__all__ = ["PlantStoreClient"]
+```
+
+### Add to `.fernignore`
+
+Protect your custom code from being overwritten:
+
+```diff title=".fernignore"
++ src/wrapper
++ src/__init__.py
+```
+
+### Use the client
+
+Users can use the client with automatic JWT signing:
+
+```python
+from plant_store_sdk import PlantStoreClient
+import os
+
+client = PlantStoreClient(
+ private_key=os.environ["PRIVATE_KEY"],
+ environment="https://api.plantstore.com"
+)
+
+# JWT is automatically signed and injected for each request
+plant = client.get_plant("monstera-123")
+new_plant = client.create_plant({
+ "name": "Fiddle Leaf Fig",
+ "species": "Ficus lyrata"
+})
+```
+
+
+
+
+This approach requires overriding each method individually. You'll need to override all methods that make API calls to ensure JWT authentication is applied consistently across your SDK.
+
+
+## Other authentication patterns
+
+This same pattern works for other dynamic authentication scenarios:
+
+- **OAuth token refresh**: Automatically refresh expired access tokens before each request
+- **HMAC (Hash-based Message Authentication Code) signing**: Sign requests with HMAC signatures based on request content
+- **Rotating API keys**: Switch between multiple API keys based on rate limits
+- **Time-based tokens**: Generate tokens that include timestamps or nonces
+
+## Important considerations
+
+### Security considerations
+
+- **Secure key storage**: Never hardcode private keys; use environment variables or secure key management systems
+- **Avoid double authentication**: If your API already uses bearer token authentication in the Fern definition, be careful not to override the existing `Authorization` header. Consider using a different header name or conditionally setting the header
+
+### Performance
+
+- **Token memoization**: Consider caching tokens to avoid regenerating them on every request. You can add a simple cache that refreshes tokens before they expire
+- **Grace period**: Refresh tokens slightly before they expire (e.g., 2 seconds early) to avoid edge cases where a token expires during request processing
+
+### Header merging
+
+- **Preserve existing headers**: When injecting authentication headers, always merge with existing headers to avoid overwriting headers set by the SDK or user
+- **Request options**: Python SDKs accept `request_options` as a keyword argument that can include custom headers
+
+### Time synchronization
+
+- **Clock drift**: Be aware of potential clock drift between your client and server. Consider adding tolerance to your token expiration checks
+- **Timestamp precision**: Use Unix timestamps (seconds since epoch) for `iat` and `exp` claims to match JWT standards
+
+## Best practices
+
+- **Override all methods**: Ensure you override all API methods to maintain consistent authentication across your SDK
+- **Cache tokens appropriately**: Balance between security (shorter token lifetime) and performance (less frequent regeneration)
+- **Handle errors gracefully**: Implement retry logic for authentication failures and token refresh errors
+- **Test thoroughly**: Ensure your wrapper handles all edge cases, including concurrent requests, token expiration, and network failures
+- **Monitor token usage**: Log token generation and refresh events to help debug authentication issues in production
+
+## See also
+
+- [Adding custom code](/sdks/generators/python/custom-code) - Python-specific customization guide
+- [Python configuration](/sdks/generators/python/configuration) - Full list of configuration options
diff --git a/fern/products/sdks/overview/typescript/dynamic-authentication.mdx b/fern/products/sdks/overview/typescript/dynamic-authentication.mdx
new file mode 100644
index 000000000..16eb130fb
--- /dev/null
+++ b/fern/products/sdks/overview/typescript/dynamic-authentication.mdx
@@ -0,0 +1,195 @@
+---
+title: Dynamic authentication
+description: Implement dynamic authentication patterns like short-lived JWT signing in TypeScript SDKs using custom fetcher middleware.
+---
+
+Your API may require dynamic authentication where credentials need to be generated or refreshed for each request, such as signing short-lived JWTs or rotating tokens. TypeScript SDKs support this pattern through custom fetcher middleware.
+
+## Custom fetcher middleware
+
+The recommended way to implement dynamic authentication in TypeScript SDKs is to use a custom fetcher. This acts as middleware for all requests, allowing you to inject authentication logic in a single place without overriding individual methods.
+
+### How it works
+
+When you enable `allowCustomFetcher` in your generator configuration, the generated SDK accepts a `fetcher` parameter in the client options. This fetcher wraps all HTTP requests, giving you a single injection point for authentication logic.
+
+### Example: Short-lived JWT signing
+
+Here's how to implement JWT signing with token memoization:
+
+
+
+### Enable custom fetcher in generator configuration
+
+Add `allowCustomFetcher: true` to your `generators.yml`:
+
+```yaml title="generators.yml"
+default-group: local
+groups:
+ local:
+ generators:
+ - name: fernapi/fern-typescript-node-sdk
+ version: 0.x.x
+ output:
+ location: local-file-system
+ path: ../generated/typescript
+ config:
+ allowCustomFetcher: true
+```
+
+### Create a custom fetcher with JWT signing
+
+Create a fetcher function that wraps the default fetcher and injects JWT authentication:
+
+```typescript title="src/wrapper/jwtFetcher.ts"
+import * as jwt from "jsonwebtoken";
+import { fetcher as defaultFetcher, type FetchFunction } from "../core/fetcher";
+
+export function createJwtFetcher(privateKey: string): FetchFunction {
+ // Cache token to avoid regenerating on every request
+ let cachedToken: { value: string; expiresAt: number } | undefined;
+
+ return async (args) => {
+ const now = Math.floor(Date.now() / 1000);
+
+ // Regenerate token if expired or about to expire (within 2 seconds)
+ if (!cachedToken || cachedToken.expiresAt - 2 <= now) {
+ const payload = {
+ iat: now,
+ exp: now + 15, // Token valid for 15 seconds
+ };
+ const token = jwt.sign(payload, privateKey, { algorithm: "RS256" });
+ cachedToken = { value: token, expiresAt: payload.exp };
+ }
+
+ // Inject JWT into request headers
+ const headers = {
+ ...(args.headers ?? {}),
+ Authorization: `Bearer ${cachedToken.value}`,
+ };
+
+ // Call the default fetcher with modified headers
+ return defaultFetcher({ ...args, headers });
+ };
+}
+```
+
+### Create a wrapper client
+
+Extend the generated client to use the custom fetcher:
+
+```typescript title="src/wrapper/PlantStoreClient.ts"
+import { PlantStoreClient as FernClient } from "../Client";
+import { createJwtFetcher } from "./jwtFetcher";
+
+// Infer the exact options type from the generated client's constructor
+type FernClientOptions = ConstructorParameters[0];
+// Accept all options except 'fetcher', and add our 'privateKey'
+type Options = Omit & { privateKey: string };
+
+export class PlantStoreClient extends FernClient {
+ constructor(options: Options) {
+ // Extract privateKey and pass all other options to the parent
+ const { privateKey, ...clientOptions } = options;
+ super({
+ ...clientOptions,
+ fetcher: createJwtFetcher(privateKey),
+ });
+ }
+}
+```
+
+
+This pattern uses `ConstructorParameters` to infer the exact options type from the generated client, ensuring compatibility with all client options (headers, timeoutInSeconds, maxRetries, etc.) without hardcoding them. This keeps the wrapper future-proof as the generator adds new options.
+
+
+### Export the wrapper client
+
+Update your `index.ts` to export the wrapper instead of the generated client:
+
+```typescript title="src/index.ts"
+export { PlantStoreClient } from "./wrapper/PlantStoreClient";
+export * from "./api"; // Export types
+```
+
+### Add to `.fernignore`
+
+Protect your custom code from being overwritten:
+
+```diff title=".fernignore"
++ src/wrapper
++ src/index.ts
+```
+
+### Use the client
+
+Users can use the client with automatic JWT signing on all requests:
+
+```typescript
+import { PlantStoreClient } from "plant-store-sdk";
+
+const client = new PlantStoreClient({
+ privateKey: process.env.PRIVATE_KEY,
+ environment: "https://api.plantstore.com",
+});
+
+// JWT is automatically signed and injected for each request
+const plant = await client.plants.get("monstera-123");
+const newPlant = await client.plants.create({
+ name: "Fiddle Leaf Fig",
+ species: "Ficus lyrata"
+});
+```
+
+
+
+## Other authentication patterns
+
+This same pattern works for other dynamic authentication scenarios:
+
+- **OAuth token refresh**: Automatically refresh expired access tokens before each request
+- **HMAC (Hash-based Message Authentication Code) signing**: Sign requests with HMAC signatures based on request content
+- **Rotating API keys**: Switch between multiple API keys based on rate limits
+- **Time-based tokens**: Generate tokens that include timestamps or nonces
+
+## Important considerations
+
+### Custom fetcher requirements
+
+- **Generator configuration**: The `allowCustomFetcher` option must be enabled in your `generators.yml` for the `fetcher` parameter to be available in `BaseClientOptions`
+- **Import path**: Import the default fetcher from `../core/fetcher` (or the appropriate path in your generated SDK) to wrap it with your custom logic
+- **Preserve all arguments**: When wrapping the default fetcher, ensure you pass through all arguments to maintain compatibility with the SDK's internal behavior
+
+### Security considerations
+
+- **Server-side only**: Never expose private keys in browser environments. JWT signing with private keys should only be done in server-side code (Node.js, Deno, Bun)
+- **Secure key storage**: Never hardcode private keys; use environment variables or secure key management systems
+- **Avoid double authentication**: If your API already uses bearer token authentication in the Fern definition, be careful not to override the existing `Authorization` header. Consider using a different header name or conditionally setting the header
+
+### Performance and concurrency
+
+- **Token memoization**: Cache tokens to avoid regenerating them on every request. The example above caches tokens and refreshes them 2 seconds before expiration
+- **Thread safety**: The memoization pattern shown is safe for concurrent requests in JavaScript's single-threaded event loop, but be mindful of race conditions in other environments
+- **Grace period**: Refresh tokens slightly before they expire (e.g., 2 seconds early) to avoid edge cases where a token expires during request processing
+
+### Header merging
+
+- **Preserve existing headers**: When injecting authentication headers, always spread existing headers to avoid overwriting headers set by the SDK or user
+- **Header precedence**: Headers are merged in order: SDK defaults → client options → request options → custom fetcher. Your custom fetcher runs last and can override previous headers
+
+### Time synchronization
+
+- **Clock drift**: Be aware of potential clock drift between your client and server. Consider adding tolerance to your token expiration checks
+- **Timestamp precision**: Use Unix timestamps (seconds since epoch) for `iat` and `exp` claims to match JWT standards
+
+## Best practices
+
+- **Cache tokens appropriately**: Balance between security (shorter token lifetime) and performance (less frequent regeneration)
+- **Handle errors gracefully**: Implement retry logic for authentication failures and token refresh errors
+- **Test thoroughly**: Ensure your wrapper handles all edge cases, including concurrent requests, token expiration, and network failures
+- **Monitor token usage**: Log token generation and refresh events to help debug authentication issues in production
+
+## See also
+
+- [Adding custom code](/sdks/generators/typescript/custom-code) - TypeScript-specific customization guide
+- [TypeScript configuration](/sdks/generators/typescript/configuration) - Full list of configuration options
diff --git a/fern/products/sdks/sdks.yml b/fern/products/sdks/sdks.yml
index ac3c9b610..fea04a3b9 100644
--- a/fern/products/sdks/sdks.yml
+++ b/fern/products/sdks/sdks.yml
@@ -41,6 +41,9 @@ navigation:
- page: Adding custom code
path: ./overview/typescript/custom-code.mdx
slug: custom-code
+ - page: Dynamic authentication
+ path: ./overview/typescript/dynamic-authentication.mdx
+ slug: dynamic-authentication
- page: Enabling the serde layer
path: ./overview/typescript/serde-layer.mdx
slug: serde-layer
@@ -66,6 +69,9 @@ navigation:
- page: Adding custom code
path: ./overview/python/custom-code.mdx
slug: custom-code
+ - page: Dynamic authentication
+ path: ./overview/python/dynamic-authentication.mdx
+ slug: dynamic-authentication
- changelog: ./overview/python/changelog
slug: changelog
- link: Customer showcase
@@ -223,6 +229,9 @@ navigation:
- page: Configure global headers
path: ./guides/configure-global-headers.mdx
slug: global-headers
+ - page: Dynamic authentication
+ path: ./guides/dynamic-authentication.mdx
+ slug: dynamic-authentication
- page: Configure auto-pagination
path: ./guides/configure-auto-pagination.mdx
slug: auto-pagination