auth101 0.2.1
Simple email/password authentication for Django, FastAPI, and Flask

# auth101

Simple email/password authentication for Django, FastAPI, and Flask.

Configure once, mount anywhere. `auth101` handles password hashing (Argon2), JWT tokens, and user storage so you can add authentication to any Python web app in minutes.

## Features

- Email/password sign-up, sign-in, sign-out, and session verification
- Secure password hashing with Argon2 (bcrypt fallback)
- JWT token generation and verification
- Built-in framework integrations for **FastAPI**, **Flask**, and **Django**
- Pluggable storage backends: in-memory, **SQLAlchemy**, or **Django ORM**

## Installation

```bash
pip install auth101
```

With extras for your framework and database:

```bash
pip install auth101[fastapi]      # FastAPI + Pydantic
pip install auth101[flask]        # Flask
pip install auth101[django]       # Django
pip install auth101[sqlalchemy]   # SQLAlchemy (any SQL database)
pip install auth101[all]          # Everything
```

## Quick Start

```python
from auth101 import Auth101

auth = Auth101(secret="change-me-in-production")

# Sign up
result = auth.sign_up("alice@example.com", "s3cr3t")
# {"user": {"id": "...", "email": "alice@example.com", "is_active": true}, "token": "..."}

# Sign in
result = auth.sign_in("alice@example.com", "s3cr3t")
# {"user": {...}, "token": "..."}

# Get session from token
session = auth.get_session(result["token"])
# {"user": {"id": "...", "email": "alice@example.com", "is_active": true}}

# Sign out (validates the token; client discards it)
auth.sign_out(result["token"])
# {"success": true}
```

## Configuration

```python
auth = Auth101(
    secret="your-secret-key",       # Required — used to sign JWT tokens
    db_url="sqlite:///auth.db",     # SQLAlchemy database URL (optional)
    django_model=AuthUser,          # Django model class (optional)
    token_expires_in=60 * 24 * 7,   # Token lifetime in minutes (default: 7 days)
)
```

| Parameter | Description |
|---|---|
| `secret` | **Required.** Secret key for signing JWT tokens. |
| `db_url` | SQLAlchemy connection string (e.g. `"postgresql://user:pass@localhost/db"`). Creates tables automatically. |
| `django_model` | Django model class with `id`, `email`, `password_hash`, `is_active` fields. |
| `token_expires_in` | Token expiration in minutes. Default: `10080` (7 days). |

> **Note:** Provide at most one of `db_url` or `django_model`. If neither is given, an in-memory store is used (great for tests and demos).

## Core API

All methods return plain dicts, so they work with any framework or without one.

### `auth.sign_up(email, password)`

Register a new user. Returns `{"user": {...}, "token": "..."}` on success.

### `auth.sign_in(email, password)`

Authenticate an existing user. Returns `{"user": {...}, "token": "..."}` on success.

### `auth.sign_out(token)`

Validate a token and acknowledge sign-out. Returns `{"success": True}`. The client is responsible for discarding the token (JWT is stateless).

### `auth.get_session(token)`

Return the user associated with a token. Returns `{"user": {...}}` on success.

### `auth.verify_token(token)`

Verify a bearer token and return the corresponding `User` object, or `None`.

### Error Responses

On failure, methods return `{"error": {"message": "...", "code": "..."}}` with one of these codes:

| Code | Meaning |
|---|---|
| `VALIDATION_ERROR` | Missing or invalid email/password |
| `USER_EXISTS` | Email already registered |
| `INVALID_CREDENTIALS` | Wrong email or password |
| `INVALID_TOKEN` | Token is malformed or expired |
| `UNAUTHORIZED` | No valid token provided |
| `USER_NOT_FOUND` | Token valid but user no longer exists |

## Framework Integrations

### FastAPI

```bash
pip install auth101[fastapi] sqlalchemy
```

```python
from fastapi import Depends, FastAPI
from auth101 import Auth101

auth = Auth101(
    secret="change-me-in-production",
    db_url="sqlite:///auth.db",
)

app = FastAPI()

# Mount auth endpoints: POST /auth/sign-up/email, /auth/sign-in/email,
#                       POST /auth/sign-out, GET /auth/session
app.include_router(auth.fastapi_router(), prefix="/auth", tags=["auth"])

# Dependency that resolves to the authenticated User or raises 401
CurrentUser = auth.fastapi_current_user()

@app.get("/me")
async def me(user=Depends(CurrentUser)):
    return {"id": user.id, "email": user.email}
```

### Flask

```bash
pip install auth101[flask] sqlalchemy
```

```python
from flask import Flask, g, jsonify
from auth101 import Auth101

auth = Auth101(
    secret="change-me-in-production",
    db_url="sqlite:///auth.db",
)

app = Flask(__name__)

# Mount auth endpoints under /auth
app.register_blueprint(auth.flask_blueprint(), url_prefix="/auth")

# Decorator that sets g.auth_user or returns 401
login_required = auth.flask_login_required()

@app.get("/me")
@login_required
def me():
    return jsonify({"id": g.auth_user.id, "email": g.auth_user.email})
```

### Django

```bash
pip install auth101[django]
```

**1. Create your model** (`myapp/models.py`):

```python
import uuid
from django.db import models

class AuthUser(models.Model):
    id            = models.CharField(max_length=36, primary_key=True,
                                     default=lambda: str(uuid.uuid4()))
    email         = models.EmailField(unique=True, db_index=True)
    password_hash = models.CharField(max_length=255)
    is_active     = models.BooleanField(default=True)

    class Meta:
        db_table = "auth_users"
```

Then run `python manage.py makemigrations && python manage.py migrate`.

**2. Configure auth101** (`myapp/auth.py`):

```python
from django.conf import settings
from auth101 import Auth101
from myapp.models import AuthUser

auth = Auth101(
    secret=settings.SECRET_KEY,
    django_model=AuthUser,
)

Auth101Middleware = auth.get_django_middleware()
login_required = auth.django_login_required()
```

**3. Add middleware** (`settings.py`):

```python
MIDDLEWARE = [
    "myapp.auth.Auth101Middleware",   # sets request.auth_user on every request
    ...
]
```

**4. Mount URLs** (`urls.py`):

```python
from django.urls import include, path
from myapp.auth import auth

urlpatterns = [
    path("auth/", include(auth.django_urls())),
    ...
]
```

**5. Protect views** (`myapp/views.py`):

```python
from django.http import JsonResponse
from myapp.auth import login_required

@login_required
def profile(request):
    return JsonResponse({"email": request.auth_user.email})
```

## Auth Endpoints

All framework integrations expose the same four endpoints (relative to the mount prefix):

| Method | Path | Body / Header | Response |
|---|---|---|---|
| POST | `/sign-up/email` | `{"email": "...", "password": "..."}` | `{"user": {...}, "token": "..."}` |
| POST | `/sign-in/email` | `{"email": "...", "password": "..."}` | `{"user": {...}, "token": "..."}` |
| POST | `/sign-out` | `Authorization: Bearer <token>` | `{"success": true}` |
| GET | `/session` | `Authorization: Bearer <token>` | `{"user": {...}}` |

## Database Persistence

### SQLAlchemy

Pass a `db_url` and tables are created automatically:

```python
auth = Auth101(
    secret="...",
    db_url="postgresql://user:pass@localhost/mydb",
)
```

Works with any SQLAlchemy-supported database: PostgreSQL, MySQL, SQLite, etc.

### Django ORM

Pass a `django_model` with the required fields (`id`, `email`, `password_hash`, `is_active`):

```python
auth = Auth101(
    secret=settings.SECRET_KEY,
    django_model=AuthUser,
)
```

### In-Memory (default)

When no `db_url` or `django_model` is provided, an in-memory store is used. Useful for testing and quick demos -- data is lost when the process exits.

## License

MIT