ayz-auth


Nameayz-auth JSON
Version 0.2.14 PyPI version JSON
download
home_pageNone
SummaryFastAPI middleware for Stytch B2B authentication with Redis caching
upload_time2025-07-22 21:04:57
maintainerNone
docs_urlNone
authorNone
requires_python>=3.8
licenseMIT
keywords authentication b2b fastapi middleware stytch
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # ayz-auth

FastAPI middleware for Stytch B2B authentication with Redis caching.

## Overview

`ayz-auth` is a lightweight, production-ready authentication middleware for FastAPI applications using Stytch B2B authentication services. It provides session token verification with Redis caching for optimal performance and includes comprehensive error handling and logging.

## Features

- ๐Ÿ” **Stytch B2B Integration**: Seamless integration with Stytch B2B authentication
- โšก **Redis Caching**: Intelligent caching to reduce API calls and improve performance
- ๐Ÿš€ **FastAPI Native**: Built specifically for FastAPI with proper dependency injection
- ๐Ÿ“ **Type Safe**: Full Pydantic models with type hints throughout
- ๐Ÿ›ก๏ธ **Security First**: Secure token handling with configurable logging levels
- ๐Ÿ”ง **Configurable**: Environment-based configuration with sensible defaults
- ๐Ÿ“Š **Comprehensive Logging**: Structured logging with sensitive data protection
- ๐Ÿงช **Well Tested**: Comprehensive test suite with mocking support

## Installation

```bash
pip install ayz-auth
```

Or with UV:

```bash
uv add ayz-auth
```

## Quick Start

### 1. Environment Configuration

Create a `.env` file or set environment variables:

```bash
STYTCH_PROJECT_ID=your_project_id
STYTCH_SECRET=your_secret_key
STYTCH_ENV=test  # or "live" for production
# STYTCH_ORGANIZATION_ID=your_org_id  # optional, only needed for member search operations
REDIS_URL=redis://localhost:6379
```

### 2. Basic Usage

```python
from fastapi import FastAPI, Depends
from ayz_auth import verify_auth, StytchContext

app = FastAPI()

@app.get("/protected")
async def protected_route(user: StytchContext = Depends(verify_auth)):
    return {
        "message": f"Hello {user.member_email}",
        "member_id": user.member_id,
        "organization_id": user.organization_id
    }

@app.get("/user-info")
async def get_user_info(user: StytchContext = Depends(verify_auth)):
    return {
        "member_id": user.member_id,
        "email": user.member_email,
        "name": user.member_name,
        "organization_id": user.organization_id,
        "session_expires_at": user.session_expires_at,
        "authentication_factors": user.authentication_factors
    }
```

### 3. Optional Authentication

For endpoints that work with or without authentication:

```python
from typing import Optional
from ayz_auth import verify_auth_optional

@app.get("/optional-auth")
async def optional_route(user: Optional[StytchContext] = Depends(verify_auth_optional)):
    if user:
        return {"message": f"Hello {user.member_email}"}
    else:
        return {"message": "Hello anonymous user"}
```

### 4. Custom Authentication Requirements

Create custom dependencies with additional requirements:

```python
from ayz_auth import create_auth_dependency

# Require specific custom claims
admin_auth = create_auth_dependency(required_claims=["admin"])
moderator_auth = create_auth_dependency(required_claims=["moderator", "verified"])

# Require specific authentication factors
mfa_auth = create_auth_dependency(required_factors=["mfa"])

@app.get("/admin")
async def admin_route(user: StytchContext = Depends(admin_auth)):
    return {"message": "Admin access granted"}

@app.get("/sensitive")
async def sensitive_route(user: StytchContext = Depends(mfa_auth)):
    return {"message": "MFA verified access"}
```

## Configuration

All configuration is handled through environment variables with the `STYTCH_` prefix:

| Variable | Default | Description |
|----------|---------|-------------|
| `STYTCH_PROJECT_ID` | *required* | Your Stytch project ID |
| `STYTCH_SECRET` | *required* | Your Stytch secret key |
| `STYTCH_ENV` | `test` | Stytch environment (`test` or `live`) |
| `STYTCH_REDIS_URL` | `redis://localhost:6379` | Redis connection URL |
| `STYTCH_REDIS_PASSWORD` | `None` | Redis password (if required) |
| `STYTCH_REDIS_DB` | `0` | Redis database number |
| `STYTCH_CACHE_TTL` | `300` | Cache TTL in seconds (5 minutes) |
| `STYTCH_CACHE_PREFIX` | `ayz_auth` | Redis key prefix |
| `STYTCH_LOG_LEVEL` | `INFO` | Logging level |
| `STYTCH_LOG_SENSITIVE_DATA` | `False` | Log sensitive data (never in production) |
| `STYTCH_REQUEST_TIMEOUT` | `10` | Request timeout in seconds |
| `STYTCH_MAX_RETRIES` | `3` | Maximum retry attempts |

## StytchContext Model

The `StytchContext` model contains all the essential session data from Stytch:

```python
class StytchContext(BaseModel):
    # Core identifiers
    member_id: str
    session_id: str
    organization_id: str
    
    # Session timing
    session_started_at: datetime
    session_expires_at: datetime
    session_last_accessed_at: datetime
    authenticated_at: datetime
    
    # Member information
    member_email: Optional[str]
    member_name: Optional[str]
    
    # Session metadata
    session_custom_claims: Dict[str, Any]
    authentication_factors: List[str]
    raw_session_data: Dict[str, Any]
    
    # Utility properties
    @property
    def is_expired(self) -> bool: ...
    
    @property
    def time_until_expiry(self) -> Optional[float]: ...
```

## Error Handling

The middleware provides structured error responses:

```python
# 401 Unauthorized - Missing or invalid token
{
    "error": "authentication_failed",
    "message": "Authorization header is required",
    "type": "token_extraction"
}

# 401 Unauthorized - Token verification failed
{
    "error": "authentication_failed", 
    "message": "Invalid or expired session token",
    "type": "token_verification"
}

# 503 Service Unavailable - Stytch API issues
{
    "error": "service_unavailable",
    "message": "Authentication service temporarily unavailable", 
    "type": "stytch_api"
}

# 403 Forbidden - Insufficient permissions (custom auth)
{
    "error": "insufficient_permissions",
    "message": "Missing required claims: ['admin']",
    "type": "authorization"
}

# 403 Forbidden - Insufficient authentication factors (custom auth)
{
    "error": "insufficient_authentication",
    "message": "Missing required authentication factors: ['mfa']",
    "type": "authorization"
}
```

## Caching Strategy

The middleware implements a two-tier verification system:

1. **Redis Cache Check**: Fast lookup of previously verified tokens
2. **Stytch API Fallback**: Fresh verification when cache misses

Cache entries automatically expire based on the session expiration time, ensuring security while maximizing performance.

## Integration with Your User System

Since the middleware only returns Stytch session data, you can easily integrate it with your existing user system:

```python
from your_app.models import User
from your_app.database import get_user_by_stytch_member_id

@app.get("/profile")
async def get_profile(stytch: StytchContext = Depends(verify_auth)):
    # Use the member_id to fetch your user data
    user = await get_user_by_stytch_member_id(stytch.member_id)
    
    if not user:
        raise HTTPException(404, "User not found")
    
    # Check permissions using your user model
    if "read_profile" not in user.permissions:
        raise HTTPException(403, "Insufficient permissions")
    
    return {
        "stytch_data": stytch.to_dict(),
        "user_data": user.to_dict()
    }
```

## Development

### Running Tests

```bash
# Install development dependencies
uv sync --dev

# Run tests
pytest

# Run tests with coverage
pytest --cov=ayz_auth
```

### Code Quality

```bash
# Format code
black src/ tests/
isort src/ tests/

# Lint code
ruff check src/ tests/

# Type checking
mypy src/
```

## Contributing

1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests for new functionality
5. Ensure all tests pass
6. Submit a pull request

## License

MIT License - see LICENSE file for details.

## Support

For issues and questions:

- GitHub Issues: [https://github.com/brandsoulmates/ayz-auth/issues](https://github.com/brandsoulmates/ayz-auth/issues)
- Documentation: [https://github.com/brandsoulmates/ayz-auth](https://github.com/brandsoulmates/ayz-auth)

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "ayz-auth",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": null,
    "keywords": "authentication, b2b, fastapi, middleware, stytch",
    "author": null,
    "author_email": "Ayzenberg <dev@ayzenberg.com>",
    "download_url": "https://files.pythonhosted.org/packages/35/69/63c8788dff5ee876dd5babaaa5561e198efe4da072ed136339e9867c6c47/ayz_auth-0.2.14.tar.gz",
    "platform": null,
    "description": "# ayz-auth\n\nFastAPI middleware for Stytch B2B authentication with Redis caching.\n\n## Overview\n\n`ayz-auth` is a lightweight, production-ready authentication middleware for FastAPI applications using Stytch B2B authentication services. It provides session token verification with Redis caching for optimal performance and includes comprehensive error handling and logging.\n\n## Features\n\n- \ud83d\udd10 **Stytch B2B Integration**: Seamless integration with Stytch B2B authentication\n- \u26a1 **Redis Caching**: Intelligent caching to reduce API calls and improve performance\n- \ud83d\ude80 **FastAPI Native**: Built specifically for FastAPI with proper dependency injection\n- \ud83d\udcdd **Type Safe**: Full Pydantic models with type hints throughout\n- \ud83d\udee1\ufe0f **Security First**: Secure token handling with configurable logging levels\n- \ud83d\udd27 **Configurable**: Environment-based configuration with sensible defaults\n- \ud83d\udcca **Comprehensive Logging**: Structured logging with sensitive data protection\n- \ud83e\uddea **Well Tested**: Comprehensive test suite with mocking support\n\n## Installation\n\n```bash\npip install ayz-auth\n```\n\nOr with UV:\n\n```bash\nuv add ayz-auth\n```\n\n## Quick Start\n\n### 1. Environment Configuration\n\nCreate a `.env` file or set environment variables:\n\n```bash\nSTYTCH_PROJECT_ID=your_project_id\nSTYTCH_SECRET=your_secret_key\nSTYTCH_ENV=test  # or \"live\" for production\n# STYTCH_ORGANIZATION_ID=your_org_id  # optional, only needed for member search operations\nREDIS_URL=redis://localhost:6379\n```\n\n### 2. Basic Usage\n\n```python\nfrom fastapi import FastAPI, Depends\nfrom ayz_auth import verify_auth, StytchContext\n\napp = FastAPI()\n\n@app.get(\"/protected\")\nasync def protected_route(user: StytchContext = Depends(verify_auth)):\n    return {\n        \"message\": f\"Hello {user.member_email}\",\n        \"member_id\": user.member_id,\n        \"organization_id\": user.organization_id\n    }\n\n@app.get(\"/user-info\")\nasync def get_user_info(user: StytchContext = Depends(verify_auth)):\n    return {\n        \"member_id\": user.member_id,\n        \"email\": user.member_email,\n        \"name\": user.member_name,\n        \"organization_id\": user.organization_id,\n        \"session_expires_at\": user.session_expires_at,\n        \"authentication_factors\": user.authentication_factors\n    }\n```\n\n### 3. Optional Authentication\n\nFor endpoints that work with or without authentication:\n\n```python\nfrom typing import Optional\nfrom ayz_auth import verify_auth_optional\n\n@app.get(\"/optional-auth\")\nasync def optional_route(user: Optional[StytchContext] = Depends(verify_auth_optional)):\n    if user:\n        return {\"message\": f\"Hello {user.member_email}\"}\n    else:\n        return {\"message\": \"Hello anonymous user\"}\n```\n\n### 4. Custom Authentication Requirements\n\nCreate custom dependencies with additional requirements:\n\n```python\nfrom ayz_auth import create_auth_dependency\n\n# Require specific custom claims\nadmin_auth = create_auth_dependency(required_claims=[\"admin\"])\nmoderator_auth = create_auth_dependency(required_claims=[\"moderator\", \"verified\"])\n\n# Require specific authentication factors\nmfa_auth = create_auth_dependency(required_factors=[\"mfa\"])\n\n@app.get(\"/admin\")\nasync def admin_route(user: StytchContext = Depends(admin_auth)):\n    return {\"message\": \"Admin access granted\"}\n\n@app.get(\"/sensitive\")\nasync def sensitive_route(user: StytchContext = Depends(mfa_auth)):\n    return {\"message\": \"MFA verified access\"}\n```\n\n## Configuration\n\nAll configuration is handled through environment variables with the `STYTCH_` prefix:\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `STYTCH_PROJECT_ID` | *required* | Your Stytch project ID |\n| `STYTCH_SECRET` | *required* | Your Stytch secret key |\n| `STYTCH_ENV` | `test` | Stytch environment (`test` or `live`) |\n| `STYTCH_REDIS_URL` | `redis://localhost:6379` | Redis connection URL |\n| `STYTCH_REDIS_PASSWORD` | `None` | Redis password (if required) |\n| `STYTCH_REDIS_DB` | `0` | Redis database number |\n| `STYTCH_CACHE_TTL` | `300` | Cache TTL in seconds (5 minutes) |\n| `STYTCH_CACHE_PREFIX` | `ayz_auth` | Redis key prefix |\n| `STYTCH_LOG_LEVEL` | `INFO` | Logging level |\n| `STYTCH_LOG_SENSITIVE_DATA` | `False` | Log sensitive data (never in production) |\n| `STYTCH_REQUEST_TIMEOUT` | `10` | Request timeout in seconds |\n| `STYTCH_MAX_RETRIES` | `3` | Maximum retry attempts |\n\n## StytchContext Model\n\nThe `StytchContext` model contains all the essential session data from Stytch:\n\n```python\nclass StytchContext(BaseModel):\n    # Core identifiers\n    member_id: str\n    session_id: str\n    organization_id: str\n    \n    # Session timing\n    session_started_at: datetime\n    session_expires_at: datetime\n    session_last_accessed_at: datetime\n    authenticated_at: datetime\n    \n    # Member information\n    member_email: Optional[str]\n    member_name: Optional[str]\n    \n    # Session metadata\n    session_custom_claims: Dict[str, Any]\n    authentication_factors: List[str]\n    raw_session_data: Dict[str, Any]\n    \n    # Utility properties\n    @property\n    def is_expired(self) -> bool: ...\n    \n    @property\n    def time_until_expiry(self) -> Optional[float]: ...\n```\n\n## Error Handling\n\nThe middleware provides structured error responses:\n\n```python\n# 401 Unauthorized - Missing or invalid token\n{\n    \"error\": \"authentication_failed\",\n    \"message\": \"Authorization header is required\",\n    \"type\": \"token_extraction\"\n}\n\n# 401 Unauthorized - Token verification failed\n{\n    \"error\": \"authentication_failed\", \n    \"message\": \"Invalid or expired session token\",\n    \"type\": \"token_verification\"\n}\n\n# 503 Service Unavailable - Stytch API issues\n{\n    \"error\": \"service_unavailable\",\n    \"message\": \"Authentication service temporarily unavailable\", \n    \"type\": \"stytch_api\"\n}\n\n# 403 Forbidden - Insufficient permissions (custom auth)\n{\n    \"error\": \"insufficient_permissions\",\n    \"message\": \"Missing required claims: ['admin']\",\n    \"type\": \"authorization\"\n}\n\n# 403 Forbidden - Insufficient authentication factors (custom auth)\n{\n    \"error\": \"insufficient_authentication\",\n    \"message\": \"Missing required authentication factors: ['mfa']\",\n    \"type\": \"authorization\"\n}\n```\n\n## Caching Strategy\n\nThe middleware implements a two-tier verification system:\n\n1. **Redis Cache Check**: Fast lookup of previously verified tokens\n2. **Stytch API Fallback**: Fresh verification when cache misses\n\nCache entries automatically expire based on the session expiration time, ensuring security while maximizing performance.\n\n## Integration with Your User System\n\nSince the middleware only returns Stytch session data, you can easily integrate it with your existing user system:\n\n```python\nfrom your_app.models import User\nfrom your_app.database import get_user_by_stytch_member_id\n\n@app.get(\"/profile\")\nasync def get_profile(stytch: StytchContext = Depends(verify_auth)):\n    # Use the member_id to fetch your user data\n    user = await get_user_by_stytch_member_id(stytch.member_id)\n    \n    if not user:\n        raise HTTPException(404, \"User not found\")\n    \n    # Check permissions using your user model\n    if \"read_profile\" not in user.permissions:\n        raise HTTPException(403, \"Insufficient permissions\")\n    \n    return {\n        \"stytch_data\": stytch.to_dict(),\n        \"user_data\": user.to_dict()\n    }\n```\n\n## Development\n\n### Running Tests\n\n```bash\n# Install development dependencies\nuv sync --dev\n\n# Run tests\npytest\n\n# Run tests with coverage\npytest --cov=ayz_auth\n```\n\n### Code Quality\n\n```bash\n# Format code\nblack src/ tests/\nisort src/ tests/\n\n# Lint code\nruff check src/ tests/\n\n# Type checking\nmypy src/\n```\n\n## Contributing\n\n1. Fork the repository\n2. Create a feature branch\n3. Make your changes\n4. Add tests for new functionality\n5. Ensure all tests pass\n6. Submit a pull request\n\n## License\n\nMIT License - see LICENSE file for details.\n\n## Support\n\nFor issues and questions:\n\n- GitHub Issues: [https://github.com/brandsoulmates/ayz-auth/issues](https://github.com/brandsoulmates/ayz-auth/issues)\n- Documentation: [https://github.com/brandsoulmates/ayz-auth](https://github.com/brandsoulmates/ayz-auth)\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "FastAPI middleware for Stytch B2B authentication with Redis caching",
    "version": "0.2.14",
    "project_urls": {
        "Homepage": "https://github.com/ayzenberg/ayz-auth",
        "Issues": "https://github.com/ayzenberg/ayz-auth/issues",
        "Repository": "https://github.com/ayzenberg/ayz-auth"
    },
    "split_keywords": [
        "authentication",
        " b2b",
        " fastapi",
        " middleware",
        " stytch"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "46aae6dc0053492cbe2133d617b1677a184bdaa17a29fd8be3582db12fd2b972",
                "md5": "292333ea44c1b04915cb0b8b7444a9b5",
                "sha256": "827b22767dab7c779adb4774408879f02936cf7f6ef3099c83dd6d4ade55ad89"
            },
            "downloads": -1,
            "filename": "ayz_auth-0.2.14-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "292333ea44c1b04915cb0b8b7444a9b5",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 22730,
            "upload_time": "2025-07-22T21:04:56",
            "upload_time_iso_8601": "2025-07-22T21:04:56.746561Z",
            "url": "https://files.pythonhosted.org/packages/46/aa/e6dc0053492cbe2133d617b1677a184bdaa17a29fd8be3582db12fd2b972/ayz_auth-0.2.14-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "356963c8788dff5ee876dd5babaaa5561e198efe4da072ed136339e9867c6c47",
                "md5": "d233248dcd52bfaa4093ccae4149fab0",
                "sha256": "ddc7944d3eb2ce4598f3c3bb5d803d737cde6f349735ca7ab075333f149631e5"
            },
            "downloads": -1,
            "filename": "ayz_auth-0.2.14.tar.gz",
            "has_sig": false,
            "md5_digest": "d233248dcd52bfaa4093ccae4149fab0",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 33265,
            "upload_time": "2025-07-22T21:04:57",
            "upload_time_iso_8601": "2025-07-22T21:04:57.940242Z",
            "url": "https://files.pythonhosted.org/packages/35/69/63c8788dff5ee876dd5babaaa5561e198efe4da072ed136339e9867c6c47/ayz_auth-0.2.14.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-07-22 21:04:57",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "ayzenberg",
    "github_project": "ayz-auth",
    "github_not_found": true,
    "lcname": "ayz-auth"
}
        
Elapsed time: 1.63494s