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

  1. Use Strong Keys: Use cryptographically secure random keys

  2. Short Expiration: Use reasonable token expiration times

  3. Rotate Keys: Regularly rotate signing keys

  4. Validate All Claims: Don’t skip important claim validations

  5. 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]
abstract async create_user_from_token(token: JWTAuthorizationCredentials) User[source]
abstract async load_jwks() JWKS[source]
abstract async verify_token(token: JWTAuthorizationCredentials) bool[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

jwt_algorithm: str | None
jwt_secret_key: str | None
jwt_token_verification_disabled: bool | None
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].