|
| 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