Skip to content
GitHub

Architecture

Internal design of the lexigram-auth package.


lexigram-auth is the security layer, depending only on lexigram and lexigram-contracts. Other packages consume auth capabilities through protocols resolved from the DI container — never direct imports.

flowchart BT
    App[Application code]
    Auth[auth — AuthBundleProvider<br/>Authentication · Tokens · Sessions<br/>Authorization · RBAC · ABAC]
    Core[lexigram — DI · Config · Events · Hooks]
    Contracts[lexigram-contracts — AuthenticatorProtocol · AuthorizerProtocol<br/>TokenManagerProtocol · PasswordHasherProtocol<br/>UserStoreProtocol · SessionRepositoryProtocol]
    App --> Auth --> Core --> Contracts
    Auth --> Contracts

sequenceDiagram
    actor Client
    participant MW as Auth Middleware
    participant AS as AuthenticationService
    participant LM as LoginAttemptTracker
    participant US as UserStore
    participant TM as JWTTokenManager
    participant EB as EventBus

    Client->>MW: POST /auth/login (email, password)
    MW->>AS: authenticate_user(email, password)
    AS->>LM: is_locked(email)
    LM-->>AS: False
    AS->>US: get_user_by_email(email)
    AS->>US: get_credentials(user_id)
    US-->>AS: User + UserCredentials
    AS->>AS: PasswordHasher.verify(password, hash)
    alt Valid credentials
        AS->>LM: clear(email)
        AS->>TM: create_token_pair(user)
        TM-->>AS: AuthToken (access + refresh)
        AS->>EB: publish(UserLoggedIn)
        AS-->>MW: Ok(User)
        MW-->>Client: 200 + JWT tokens
    else Invalid credentials
        AS->>LM: record_failure(email)
        AS->>EB: publish(UserLoginFailed)
        AS-->>MW: Err(InvalidCredentialsError)
        MW-->>Client: 401
    else Account locked
        AS->>EB: publish(UserLockedOut)
        AS-->>MW: Err(AccountLockedError)
        MW-->>Client: 423
    end

Timing-attack mitigation: PasswordHasher.verify() always runs against a dummy hash for non-existent users, preventing user-enumeration.


Combines RBAC (role-based access control) with optional ABAC attribute-based policies.

flowchart LR
    subgraph Guards
        RD[require_auth / require_roles<br/>require_permissions / optional_auth]
        WG[AuthGuard / RoleGuard<br/>PermissionGuard / CompositeGuard]
    end
    subgraph Core
        AS[AuthorizationService]
        PE[PolicyEngine · ABAC]
    end
    subgraph Storage
        YAML[Config / YAML seed]
        DB[(Database)]
    end
    Guards --> AS
    AS --> YAML
    AS --> DB
    AS --> PE

Roles support inheritance and configurable permission caching (default 300s TTL):

auth_service.set_roles({
"admin": {"permissions": ["*"]},
"editor": {"inherits": ["viewer"], "permissions": ["articles.write"]},
"viewer": {"permissions": ["articles.read"]},
})
Decorator / GuardPurpose
@require_auth()Requires valid authentication
@require_roles("admin")Requires at least one role
@require_permissions("write")Requires all listed permissions
@optional_authPopulates user if present, never blocks
AuthGuardGuardProtocol — requires authenticated user
RoleGuard("admin")GuardProtocol — requires specific role
PermissionGuard("write")GuardProtocol — requires specific permission
CompositeGuard(*guards)AND-combination of multiple guards

sequenceDiagram
    participant Container as DI Container
    participant Bundle as AuthBundleProvider<br/>(SECURITY priority, config_key="auth")
    participant Sub as 5-7 sub-providers

    Container->>Bundle: register(container)
    activate Bundle
    Bundle->>Sub: register() on each sub-provider
    deactivate Bundle

    Container->>Bundle: boot(container)
    activate Bundle
    Bundle->>Sub: boot() on each sub-provider
    deactivate Bundle

    Container->>Bundle: shutdown()
    activate Bundle
    Bundle->>Sub: shutdown() in reverse order
    deactivate Bundle
Sub-providerKey Registrations
AuthenticationProviderAuthenticationService, LoginAttemptTracker, PasswordHasherProtocol, UserStoreProtocol
TokenProviderJWTTokenManager — HS256/RS256, key rotation, blacklist, grace periods
SessionProviderSessionManagerImpl, SessionManagerProtocolmax_sessions_per_user
AuthorizationProviderAuthorizationService, AuthorizerProtocol — initial RBAC role seed
AuthAdminProviderAuthAdminContributor, PackageWidgetRenderer, widget handlers
GoogleOAuthProviderGoogleOAuthService — only when oauth2_providers.google configured
PasskeyProviderWebAuthn passkey support — only when enable_passkeys=True

src/lexigram/auth/
├── __init__.py # Lazy re-exports
├── module.py # AuthModule — @module(is_global=True)
├── config.py # AuthConfig, JWTConfig, RBACConfig, PasswordConfig, …
├── constants.py # Token/session/MFA defaults
├── exceptions.py # AuthError, AuthenticationError, TokenError, … (23 leaf types)
├── types.py # AuthStatus, UserStatus, RoleDefinition, TokenType, AuthResult
├── protocols.py # TokenValidatorProtocol, IdentityResolverProtocol
├── events.py # 11 domain events (UserLoggedIn, TokenRevoked, …)
├── hooks.py # 5 hook payloads (AuthTokenIssuedHook, …)
├── decorators.py # @require_auth, @require_roles
├── models/ # User, AuthToken, UserSession, MFARecoveryCode, APIKey
├── authn/ # Authentication — AuthenticationService, JWTTokenManager,
│ │ # PasswordHasher, APIKeyAuthenticator, OAuth2, WebAuthn,
│ │ # SAML, LDAP, MFA, key rotation, token blacklist
│ ├── schemas/ # Pydantic request/response schemas
│ └── [...] (19 modules)
├── authz/ # AuthorizationService, guards (5 modules)
├── session/ # SessionManagerImpl, SessionCookieBackend, fingerprint
├── mfa/ # MFAManager, TOTP lifecycle, recovery codes
├── policies/ # ABAC PolicyEngine, ConditionEvaluator, policy types
├── web/ # AuthMiddleware, middleware chain, GuardProtocol guards
│ └── middleware/ # 8 middleware components (JWT, session, API key, throttle, …)
├── admin/ # AuthAdminContributor, dashboard widgets, renderer
├── storage/ # UserStore, SessionStore, OAuthIdentityStore implementations
├── services/ # Result-pattern auth service
├── cli/ # CLI commands
└── di/ # Provider registration
├── bundle_provider.py # AuthBundleProvider
├── provider.py # AuthProvider (legacy)
└── sub_providers/ # 8 sub-providers

ContractSourcePurpose
AuthenticatorProtocolcontracts.auth.guardAuthentication boundary
AuthorizerProtocolcontracts.auth.guardAuthorization boundary
TokenManagerProtocolcontracts.auth.tokenToken lifecycle
PasswordHasherProtocolcontracts.auth.protocolsPassword hashing
PasswordPolicyProtocolcontracts.auth.protocolsPassword complexity
LoginAttemptTrackerProtocolcontracts.auth.protocolsAccount lockout
AuthProviderProtocolcontracts.auth.protocolsComposite auth
MFAManagerProtocolcontracts.auth.protocolsMulti-factor auth
IdentityResolverProtocolcontracts.auth.identityToken → user identity
SessionRepositoryProtocolcontracts.auth.repositoriesSession persistence
TokenBlacklistProtocolcontracts.auth.blacklistToken revocation
UserStoreProtocolcontracts.auth.storeFull user CRUD
PolicyStoreProtocolcontracts.auth.policyABAC policy storage
CacheBackendProtocolcontracts.infra.cacheDistributed state
EventBusProtocolcontracts.eventsDomain events
HookRegistryProtocolcontracts.coreLifecycle hooks
AuditLoggerProtocolcontracts.auditAudit trails

FeatureImplementationConfiguration
JWT tokensHS256/RS256/PS256, configurable expiry, audience, key rotationJWTConfig
Token blacklistJWTBlacklist — in-process + optional CacheBackendProtocolAuto-enabled with cache
Key rotationJWTKeyStore — multiple keys, grace period (default 3600s)key_rotation_grace_period
Token bindingSHA-256 hash of IP/fingerprint bound to tokenTokenBindingConfig
Password hashingArgon2id via KeyDerivationProtocolPasswordConfig
Account lockoutRolling window, configurable threshold + durationLockoutConfig
Rate limitingAuth middleware throttlelogin_rate_limit
Session managementDevice-aware sessions, optional max per usermax_sessions_per_user
MFA (TOTP)RFC 6238 — enroll, verify, recovery codesTOTP defaults (6 digits, 30s)
WebAuthn / PasskeysEC P-256 challenge-response, sign-counterenable_passkeys=True
OAuth2 / OIDCAuthlib-based, any provideroauth2_providers dict
Google OAuthFirst-class JWKS / tokeninfo verificationoauth2_providers.google
SAML / LDAPSAML + LDAP auth supportConfig sections
API keysHeader or query param lookupAPIKeyConfig

flowchart LR
    subgraph Contracts
        AE[AuthError · 001]
        TE[TokenError · 002]
        VE[VerificationError · 003]
    end
    subgraph Package
        AUTHERR[AuthenticationError · 005]
        AUTHZERR[AuthorizationError · 006]
        ICE[InvalidCredentialsError · 007]
        ALE[AccountLockedError · 008]
        UNFE[UserNotFoundError · 009]
        TE2[TokenError · 010] --> ITE[InvalidToken · 011]
        TE2 --> TEE[TokenExpired · 012]
        TE2 --> TBE[Blacklisted · 013]
        TE2 --> TIE[Invalid · 014]
        TE2 --> TAE[Audience · 015]
        TE2 --> TNTE[NotFound · 016]
        EEE[EmailExistsError · 022]
        PPE[PasswordPolicyError · 024]
        O2E[OAuth2Error · 025]
    end
    Contracts --> Package

Rules: AuthError (contracts) is the catch-all. TokenError/VerificationError are expected, recoverable — use Result. Leaf exceptions (23 total, all with LEX_ERR_AUTH_* codes) live in lexigram.auth.exceptions. Infrastructure failures propagate as exceptions, never wrapped in Result.


# AuthModule.configure(config) → AuthBundleProvider registers sub-providers
container.singleton(PasswordHasherProtocol, hasher)
container.singleton(UserStoreProtocol, user_store)
container.singleton(AuthenticationService, service)
container.singleton(JWTTokenManager, token_manager)
container.singleton(SessionManagerProtocol, session_mgr)
container.singleton(AuthorizerProtocol, auth_service)
# Module exports (resolvable by consumers):
# AuthenticatorProtocol, AuthorizerProtocol, TokenManagerProtocol,
# PasswordHasherProtocol, AuthAdminContributor, PackageWidgetRenderer,
# ActiveSessionsWidgetHandler, FailedLoginsWidgetHandler,
# TokenRefreshRateWidgetHandler

PointMechanism
Custom auth backendImplement AuthenticatorProtocol and register with higher priority
Custom token formatImplement TokenManagerProtocol (e.g., opaque tokens)
Custom user storeImplement UserStoreProtocol / UserReaderProtocol / UserWriterProtocol
Custom authorizationImplement AuthorizerProtocol — override RBAC with custom logic
Custom identity resolverImplement IdentityResolverProtocol
OAuth2 providersAdd configs in oauth2_providers section
MFA methodsImplement MFAManagerProtocol
WebAuthn passkeysSet enable_passkeys=True
Event hooksSubscribe to AuthTokenIssuedHook, AuthTokenRevokedHook, AuthUserAuthenticatedHook
Domain eventsSubscribe via EventBusProtocolUserLoggedIn, UserLockedOut, TokenRevoked, etc.
Admin widgetsRegister via AuthAdminContributor
Custom password policyImplement PasswordPolicyProtocol
Cache backendInject CacheBackendProtocol for distributed lockout/blacklist
Audit loggerInject AuditLoggerProtocol