Dynamic authentication

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:

1

Create a wrapper client

Create a custom client class that extends the generated Fern client and adds JWT signing logic:

src/wrapper/client.py
1from .client import PlantStoreClient as FernClient
2import jwt
3import time
4from typing import Optional, Dict, Any
5
6class PlantStoreClient(FernClient):
7 def __init__(self, *, private_key: str, environment: str):
8 super().__init__(environment=environment)
9 self._private_key = private_key
10
11 def _generate_jwt(self) -> str:
12 """Generate a short-lived JWT token valid for 15 seconds"""
13 now = int(time.time())
14 payload = {
15 "iat": now,
16 "exp": now + 15,
17 }
18 return jwt.encode(payload, self._private_key, algorithm="RS256")
19
20 def _with_jwt(self, request_options: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
21 """Inject JWT into request options"""
22 token = self._generate_jwt()
23 options = request_options or {}
24 headers = options.get("headers", {})
25 headers["Authorization"] = f"Bearer {token}"
26 options["headers"] = headers
27 return options
28
29 def get_plant(self, plant_id: str, *, request_options: Optional[Dict[str, Any]] = None):
30 return super().plants.get(plant_id, request_options=self._with_jwt(request_options))
31
32 def create_plant(self, request: Any, *, request_options: Optional[Dict[str, Any]] = None):
33 return super().plants.create(request, request_options=self._with_jwt(request_options))
34
35 # Override additional methods as needed...
2

Export the wrapper client

Update your __init__.py to export the wrapper instead of the generated client:

src/__init__.py
1from .wrapper.client import PlantStoreClient
2
3__all__ = ["PlantStoreClient"]
3

Add to .fernignore

Protect your custom code from being overwritten:

.fernignore
1+ src/wrapper
2+ src/__init__.py
4

Use the client

Users can use the client with automatic JWT signing:

1from plant_store_sdk import PlantStoreClient
2import os
3
4client = PlantStoreClient(
5 private_key=os.environ["PRIVATE_KEY"],
6 environment="https://api.plantstore.com"
7)
8
9# JWT is automatically signed and injected for each request
10plant = client.get_plant("monstera-123")
11new_plant = client.create_plant({
12 "name": "Fiddle Leaf Fig",
13 "species": "Ficus lyrata"
14})

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