Skip to content

Commit c4933c1

Browse files
docs: add dynamic authentication guide for JWT signing in SDKs (#1788)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Chris McDonnell <[email protected]>
1 parent 24d8c51 commit c4933c1

File tree

4 files changed

+408
-0
lines changed

4 files changed

+408
-0
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
---
2+
title: Dynamic authentication
3+
description: Implement dynamic authentication patterns like short-lived JWT signing in your SDKs.
4+
---
5+
6+
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.
7+
8+
## Language-specific guides
9+
10+
Each language has its own recommended approach for implementing dynamic authentication:
11+
12+
<Cards>
13+
<Card title="TypeScript" icon="fa-brands fa-js" href="/sdks/generators/typescript/dynamic-authentication">
14+
Use custom fetcher middleware to inject authentication logic in a single place for all requests. Supports JWT signing, OAuth token refresh, and more.
15+
</Card>
16+
<Card title="Python" icon="fa-brands fa-python" href="/sdks/generators/python/dynamic-authentication">
17+
Use method overrides to inject authentication logic for each API call. Supports JWT signing, OAuth token refresh, and more.
18+
</Card>
19+
</Cards>
20+
21+
## Common use cases
22+
23+
Dynamic authentication is useful for several scenarios:
24+
25+
- **Short-lived JWT signing**: Generate and sign JWTs that expire after a short period (e.g., 15 seconds) for enhanced security
26+
- **OAuth token refresh**: Automatically refresh expired access tokens before each request
27+
- **HMAC (Hash-based Message Authentication Code) signing**: Sign requests with HMAC signatures based on request content
28+
- **Rotating API keys**: Switch between multiple API keys based on rate limits or other criteria
29+
- **Time-based tokens**: Generate tokens that include timestamps or nonces for replay protection
30+
31+
## Important considerations
32+
33+
When implementing dynamic authentication, keep these language-agnostic considerations in mind:
34+
35+
### Security
36+
37+
- **Secure key storage**: Never hardcode private keys or secrets; use environment variables or secure key management systems
38+
- **Server-side only**: For JWT signing with private keys, ensure this is only done in server-side code, never in browser environments
39+
- **Avoid double authentication**: If your API already uses bearer token authentication in the Fern definition, be careful not to override existing authentication headers
40+
41+
### Performance
42+
43+
- **Token caching**: Cache tokens to avoid regenerating them on every request, balancing security (shorter token lifetime) with performance (less frequent regeneration)
44+
- **Grace period**: Refresh tokens slightly before they expire to avoid edge cases where a token expires during request processing
45+
- **Concurrency**: Be mindful of race conditions when caching tokens in multi-threaded environments
46+
47+
### Time synchronization
48+
49+
- **Clock drift**: Be aware of potential clock drift between your client and server; consider adding tolerance to token expiration checks
50+
- **Timestamp precision**: Use Unix timestamps (seconds since epoch) for JWT `iat` and `exp` claims to match standards
51+
52+
## See also
53+
54+
- [Adding custom code](/sdks/custom-code) - Learn more about extending generated SDKs
55+
- [TypeScript custom code](/sdks/generators/typescript/custom-code) - TypeScript-specific customization guide
56+
- [Python custom code](/sdks/generators/python/custom-code) - Python-specific customization guide
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
---
2+
title: Dynamic authentication
3+
description: Implement dynamic authentication patterns like short-lived JWT signing in Python SDKs using method overrides.
4+
---
5+
6+
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.
7+
8+
## Method override pattern
9+
10+
For Python SDKs, you can implement dynamic authentication by extending the generated client and overriding methods to inject authentication logic.
11+
12+
### Example: Short-lived JWT signing
13+
14+
Here's how to implement JWT signing for Python SDKs:
15+
16+
<Steps>
17+
18+
### Create a wrapper client
19+
20+
Create a custom client class that extends the generated Fern client and adds JWT signing logic:
21+
22+
```python title="src/wrapper/client.py"
23+
from .client import PlantStoreClient as FernClient
24+
import jwt
25+
import time
26+
from typing import Optional, Dict, Any
27+
28+
class PlantStoreClient(FernClient):
29+
def __init__(self, *, private_key: str, environment: str):
30+
super().__init__(environment=environment)
31+
self._private_key = private_key
32+
33+
def _generate_jwt(self) -> str:
34+
"""Generate a short-lived JWT token valid for 15 seconds"""
35+
now = int(time.time())
36+
payload = {
37+
"iat": now,
38+
"exp": now + 15,
39+
}
40+
return jwt.encode(payload, self._private_key, algorithm="RS256")
41+
42+
def _with_jwt(self, request_options: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
43+
"""Inject JWT into request options"""
44+
token = self._generate_jwt()
45+
options = request_options or {}
46+
headers = options.get("headers", {})
47+
headers["Authorization"] = f"Bearer {token}"
48+
options["headers"] = headers
49+
return options
50+
51+
def get_plant(self, plant_id: str, *, request_options: Optional[Dict[str, Any]] = None):
52+
return super().plants.get(plant_id, request_options=self._with_jwt(request_options))
53+
54+
def create_plant(self, request: Any, *, request_options: Optional[Dict[str, Any]] = None):
55+
return super().plants.create(request, request_options=self._with_jwt(request_options))
56+
57+
# Override additional methods as needed...
58+
```
59+
60+
### Export the wrapper client
61+
62+
Update your `__init__.py` to export the wrapper instead of the generated client:
63+
64+
```python title="src/__init__.py"
65+
from .wrapper.client import PlantStoreClient
66+
67+
__all__ = ["PlantStoreClient"]
68+
```
69+
70+
### Add to `.fernignore`
71+
72+
Protect your custom code from being overwritten:
73+
74+
```diff title=".fernignore"
75+
+ src/wrapper
76+
+ src/__init__.py
77+
```
78+
79+
### Use the client
80+
81+
Users can use the client with automatic JWT signing:
82+
83+
```python
84+
from plant_store_sdk import PlantStoreClient
85+
import os
86+
87+
client = PlantStoreClient(
88+
private_key=os.environ["PRIVATE_KEY"],
89+
environment="https://api.plantstore.com"
90+
)
91+
92+
# JWT is automatically signed and injected for each request
93+
plant = client.get_plant("monstera-123")
94+
new_plant = client.create_plant({
95+
"name": "Fiddle Leaf Fig",
96+
"species": "Ficus lyrata"
97+
})
98+
```
99+
100+
</Steps>
101+
102+
<Note>
103+
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.
104+
</Note>
105+
106+
## Other authentication patterns
107+
108+
This same pattern works for other dynamic authentication scenarios:
109+
110+
- **OAuth token refresh**: Automatically refresh expired access tokens before each request
111+
- **HMAC (Hash-based Message Authentication Code) signing**: Sign requests with HMAC signatures based on request content
112+
- **Rotating API keys**: Switch between multiple API keys based on rate limits
113+
- **Time-based tokens**: Generate tokens that include timestamps or nonces
114+
115+
## Important considerations
116+
117+
### Security considerations
118+
119+
- **Secure key storage**: Never hardcode private keys; use environment variables or secure key management systems
120+
- **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
121+
122+
### Performance
123+
124+
- **Token memoization**: Consider caching tokens to avoid regenerating them on every request. You can add a simple cache that refreshes tokens before they expire
125+
- **Grace period**: Refresh tokens slightly before they expire (e.g., 2 seconds early) to avoid edge cases where a token expires during request processing
126+
127+
### Header merging
128+
129+
- **Preserve existing headers**: When injecting authentication headers, always merge with existing headers to avoid overwriting headers set by the SDK or user
130+
- **Request options**: Python SDKs accept `request_options` as a keyword argument that can include custom headers
131+
132+
### Time synchronization
133+
134+
- **Clock drift**: Be aware of potential clock drift between your client and server. Consider adding tolerance to your token expiration checks
135+
- **Timestamp precision**: Use Unix timestamps (seconds since epoch) for `iat` and `exp` claims to match JWT standards
136+
137+
## Best practices
138+
139+
- **Override all methods**: Ensure you override all API methods to maintain consistent authentication across your SDK
140+
- **Cache tokens appropriately**: Balance between security (shorter token lifetime) and performance (less frequent regeneration)
141+
- **Handle errors gracefully**: Implement retry logic for authentication failures and token refresh errors
142+
- **Test thoroughly**: Ensure your wrapper handles all edge cases, including concurrent requests, token expiration, and network failures
143+
- **Monitor token usage**: Log token generation and refresh events to help debug authentication issues in production
144+
145+
## See also
146+
147+
- [Adding custom code](/sdks/generators/python/custom-code) - Python-specific customization guide
148+
- [Python configuration](/sdks/generators/python/configuration) - Full list of configuration options

0 commit comments

Comments
 (0)