Auth (lexigram-auth)
Authentication and authorization for the Lexigram Framework — JWT, OAuth2, SAML, RBAC, and multi-tenancy.
Overview
Section titled “Overview”Complete authentication and authorization stack for Lexigram — JWT, OAuth2, RBAC,
SAML, passkeys, and MFA. Provides a production-ready auth layer with multiple
authentication strategies, policy-based access control, session management, and
seamless integration with lexigram-web middleware.
Use AuthModule.configure() to register the auth bundle and protect routes with
@require_auth, @require_roles, and @require_permissions decorators.
Full documentation: docs.lexigram.dev
Install
Section titled “Install”uv add lexigram-auth# Optional extrasuv add "lexigram-auth[oauth2,saml]"Quick Start
Section titled “Quick Start”from lexigram import Applicationfrom lexigram.di.module import Module, modulefrom lexigram.auth import AuthModule, AuthConfig, JWTConfig
@module(imports=[ AuthModule.configure( config=AuthConfig( secret_key="your-secret-key", token=JWTConfig(secret_key="your-jwt-secret"), ) )])class AppModule(Module): pass
async def main() -> None: async with Application.boot(modules=[AppModule]) as app: # app is running — resolve services from app.container ...
if __name__ == "__main__": import asyncio
asyncio.run(main())Configuration
Section titled “Configuration”Note:
AuthConfigrequires bothsecret_keyandtoken.secret_key— pass an explicit config viaAuthModule.configure().
Option 1 — YAML file
Section titled “Option 1 — YAML file”auth: secret_key: "your-secret-key" token: secret_key: "your-jwt-secret" algorithm: "HS256" access_token_expire: "30m" rbac: enabled: true default_role: "viewer"Option 2 — Profiles + Environment Variables (recommended)
Section titled “Option 2 — Profiles + Environment Variables (recommended)”export LEX_AUTH__SECRET_KEY=your-secret-keyexport LEX_AUTH__TOKEN__SECRET_KEY=your-jwt-secretexport LEX_AUTH__TOKEN__ALGORITHM=HS256export LEX_AUTH__RBAC__DEFAULT_ROLE=viewerOption 3 — Python
Section titled “Option 3 — Python”from lexigram.auth import AuthModule, AuthConfig, JWTConfigfrom lexigram.contracts.core import Duration
config = AuthConfig( secret_key="your-secret-key", token=JWTConfig( secret_key="your-jwt-secret", algorithm="HS256", access_token_expire=Duration.minutes(30), ),)AuthModule.configure(config)Config reference
Section titled “Config reference”| Field | Default | Env var | Description |
|---|---|---|---|
secret_key | — | LEX_AUTH__SECRET_KEY | Top-level signing secret (required) |
token.secret_key | — | LEX_AUTH__TOKEN__SECRET_KEY | JWT signing secret (required) |
token.algorithm | HS256 | LEX_AUTH__TOKEN__ALGORITHM | JWT algorithm: HS256, RS256, ES256 |
token.access_token_expire | 30m | LEX_AUTH__TOKEN__ACCESS_TOKEN_EXPIRE | Access token lifetime (duration string, e.g. 30m, 1h30m) |
rbac.enabled | True | LEX_AUTH__RBAC__ENABLED | Enable RBAC |
rbac.default_role | viewer | LEX_AUTH__RBAC__DEFAULT_ROLE | Default role for new users |
Module Factory Methods
Section titled “Module Factory Methods”| Method | Description |
|---|---|
AuthModule.configure(...) | Configure with explicit AuthConfig |
AuthModule.stub() | Minimal config for testing |
Key Features
Section titled “Key Features”- JWT authentication — HS256/RS256, key rotation, token blacklisting
- OAuth2 / OIDC — authlib-backed: Google, GitHub, custom providers
- SAML 2.0 — Enterprise SSO via pysaml2
- Passkeys (WebAuthn) — FIDO2 device-based authentication
- MFA (TOTP) — Time-based one-time passwords
- RBAC — Role/permission inheritance with policy expressions
- Session management — Device-aware sessions with concurrency limits
- Token binding — IP address binding to prevent token theft
Testing
Section titled “Testing”async with Application.boot(modules=[AuthModule.stub()]) as app: # your test code ...Key Source Files
Section titled “Key Source Files”| File | What it contains |
|---|---|
src/lexigram/auth/module.py | AuthModule definition |
src/lexigram/auth/config.py | AuthConfig, JWTConfig (+ allow_unverified_dev), RBACConfig |
src/lexigram/auth/di/bundle_provider.py | AuthBundleProvider wiring |
src/lexigram/auth/di/sub_providers/token_provider.py | TokenProvider (boots policy) |
src/lexigram/auth/authn/jwt.py | JWTTokenManager implementation |
src/lexigram/auth/authn/_jwt_lifecycle.py | verify_token (enforces policy) |
src/lexigram/auth/authz/service.py | AuthorizationService |
JWT verification policy
Section titled “JWT verification policy”lexigram-auth enforces verified-only JWT decoding by default.
| Environment | Secret present | allow_unverified_dev | Behaviour |
|---|---|---|---|
PRODUCTION / STAGING | yes | any | Verified-only. Boot succeeds. |
PRODUCTION / STAGING | no | any | Raises ConfigurationError at boot. Flag ignored. |
DEVELOPMENT | yes | any | Verified-only. Boot succeeds. |
DEVELOPMENT | no | False (default) | Raises ConfigurationError at boot. |
DEVELOPMENT | no | True | Boots. Single warning logged. Tokens decoded without signature verification. |
Enable the dev opt-in
Section titled “Enable the dev opt-in”Via environment variable:
export LEX_AUTH__TOKEN__ALLOW_UNVERIFIED_DEV=trueVia Python config:
from lexigram.auth.config import AuthConfig, JWTConfig
config = AuthConfig( secret_key="any-placeholder", token=JWTConfig( secret_key="any-placeholder", allow_unverified_dev=True, ),)The allow_unverified_dev flag is silently ignored in PRODUCTION and STAGING; the
service always rejects the flag in those environments and raises if no real secret is
configured. This prevents the Piccolina-style mistake of silently trusting unverified tokens
in production when a secret env-var is missing.