JWT Authentication Provider
The JWT Authentication Provider is a generic provider that can validate JWT tokens signed with various algorithms. This provider is useful when you have your own JWT token issuing service or need to integrate with custom authentication systems.
Overview
The JWT provider supports:
Multiple Algorithms: HS256, HS384, HS512, RS256, RS384, RS512, and more
Custom Claims: Flexible claim mapping for user information
Secret Management: Support for both symmetric and asymmetric keys
Claim Validation: Standard JWT claim validation (exp, iat, iss, aud)
Custom Validation: Extensible validation logic
Configuration
Basic Configuration
from auth_middleware import JwtAuthMiddleware
from auth_middleware.providers.authn.jwt_provider import JWTProvider
from auth_middleware.providers.authn.jwt_provider_settings import JWTProviderSettings
# Configure JWT settings
jwt_settings = JWTProviderSettings(
secret_key="your-secret-key",
algorithm="HS256",
issuer="your-issuer",
audience="your-audience",
)
# Create JWT provider
jwt_provider = JWTProvider(settings=jwt_settings)
# Add to FastAPI application
app.add_middleware(JwtAuthMiddleware, auth_provider=jwt_provider)
Environment-based Configuration
For production deployments, use environment variables:
import os
from auth_middleware.providers.authn.jwt_provider_settings import JWTProviderSettings
def create_jwt_settings():
return JWTProviderSettings(
secret_key=os.getenv("JWT_SECRET_KEY"),
algorithm=os.getenv("JWT_ALGORITHM", "HS256"),
issuer=os.getenv("JWT_ISSUER"),
audience=os.getenv("JWT_AUDIENCE"),
verify_signature=os.getenv("JWT_VERIFY_SIGNATURE", "true").lower() == "true",
verify_exp=os.getenv("JWT_VERIFY_EXP", "true").lower() == "true",
verify_iat=os.getenv("JWT_VERIFY_IAT", "true").lower() == "true",
)
Token Structure
Expected JWT Claims
The JWT provider expects the following standard claims:
{
"sub": "user123", // Subject (user ID)
"name": "John Doe", // User name
"email": "john@example.com", // User email
"groups": ["user", "admin"], // User groups (optional)
"permissions": ["read", "write"], // User permissions (optional)
"iss": "your-issuer", // Issuer
"aud": "your-audience", // Audience
"exp": 1640995200, // Expiration timestamp
"iat": 1640908800, // Issued at timestamp
"custom_claim": "custom_value" // Custom claims
}
Custom Claim Mapping
You can customize how claims are mapped to user properties:
class CustomJWTProvider(JWTProvider):
def extract_user_info(self, decoded_token: dict) -> dict:
return {
"id": decoded_token.get("user_id"), # Custom user ID field
"name": decoded_token.get("full_name"), # Custom name field
"email": decoded_token.get("email_addr"), # Custom email field
"groups": decoded_token.get("roles", []), # Custom groups field
"permissions": decoded_token.get("perms", []),
"raw_token": decoded_token,
}
Integration Examples
With Custom Token Service
from fastapi import FastAPI, Depends
from auth_middleware import JwtAuthMiddleware, require_user
from auth_middleware.providers.authn.jwt_provider import JWTProvider
from auth_middleware.providers.authn.jwt_provider_settings import JWTProviderSettings
app = FastAPI(title="Custom JWT API")
# JWT configuration for your custom token service
jwt_settings = JWTProviderSettings(
secret_key="your-hmac-secret-key",
algorithm="HS256",
issuer="your-auth-service",
audience="your-api",
verify_exp=True,
leeway=30, # 30 seconds clock skew allowance
)
# Setup middleware
app.add_middleware(
JwtAuthMiddleware,
auth_provider=JWTProvider(settings=jwt_settings),
)
@app.get("/profile", dependencies=[Depends(require_user())])
async def get_profile(request):
user = request.state.current_user
return {
"user_id": user.id,
"name": user.name,
"email": user.email,
"groups": user.groups,
}
Best Practices
Security Recommendations
Use Strong Keys: Use cryptographically secure random keys
Short Expiration: Use reasonable token expiration times
Rotate Keys: Regularly rotate signing keys
Validate All Claims: Don’t skip important claim validations
Use HTTPS: Always use HTTPS to protect tokens in transit
API Reference
- class auth_middleware.providers.authn.jwt_provider.JWTProvider(settings: JWTProviderSettings | None = None, permissions_provider: PermissionsProvider | None = None, groups_provider: GroupsProvider | None = None)[source]
Bases:
object
Basic interface for a JWT authentication provider
- Parameters:
metaclass (_type_, optional) – _description_. Defaults to ABCMeta.
- __init__(settings: JWTProviderSettings | None = None, permissions_provider: PermissionsProvider | None = None, groups_provider: GroupsProvider | None = None) None [source]
- class auth_middleware.providers.authn.jwt_provider_settings.JWTProviderSettings(_case_sensitive: bool | None = None, _nested_model_default_partial_update: bool | None = None, _env_prefix: str | None = None, _env_file: DotenvType | None = PosixPath('.'), _env_file_encoding: str | None = None, _env_ignore_empty: bool | None = None, _env_nested_delimiter: str | None = None, _env_nested_max_split: int | None = None, _env_parse_none_str: str | None = None, _env_parse_enums: bool | None = None, _cli_prog_name: str | None = None, _cli_parse_args: bool | list[str] | tuple[str, ...] | None = None, _cli_settings_source: CliSettingsSource[Any] | None = None, _cli_parse_none_str: str | None = None, _cli_hide_none_type: bool | None = None, _cli_avoid_json: bool | None = None, _cli_enforce_required: bool | None = None, _cli_use_class_docs_for_groups: bool | None = None, _cli_exit_on_error: bool | None = None, _cli_prefix: str | None = None, _cli_flag_prefix_char: str | None = None, _cli_implicit_flags: bool | None = None, _cli_ignore_unknown_args: bool | None = None, _cli_kebab_case: bool | None = None, _cli_shortcuts: Mapping[str, str | list[str]] | None = None, _secrets_dir: PathType | None = None, *, jwt_secret_key: str | None = None, jwt_algorithm: str | None = 'HS256', jwt_token_verification_disabled: bool | None = False)[source]
Bases:
BaseSettings
Base settings for JWT Provider
- model_config: ClassVar[SettingsConfigDict] = {'arbitrary_types_allowed': True, 'case_sensitive': False, 'cli_avoid_json': False, 'cli_enforce_required': False, 'cli_exit_on_error': True, 'cli_flag_prefix_char': '-', 'cli_hide_none_type': False, 'cli_ignore_unknown_args': False, 'cli_implicit_flags': False, 'cli_kebab_case': False, 'cli_parse_args': None, 'cli_parse_none_str': None, 'cli_prefix': '', 'cli_prog_name': None, 'cli_shortcuts': None, 'cli_use_class_docs_for_groups': False, 'enable_decoding': True, 'env_file': '.env', 'env_file_encoding': 'utf-8', 'env_ignore_empty': False, 'env_nested_delimiter': None, 'env_nested_max_split': None, 'env_parse_enums': None, 'env_parse_none_str': None, 'env_prefix': '', 'extra': 'ignore', 'frozen': True, 'json_file': None, 'json_file_encoding': None, 'nested_model_default_partial_update': False, 'protected_namespaces': ('model_validate', 'model_dump', 'settings_customise_sources'), 'secrets_dir': None, 'toml_file': None, 'validate_default': True, 'yaml_config_section': None, 'yaml_file': None, 'yaml_file_encoding': None}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].