Metadata-Version: 2.3
Name: pyallauth
Version: 0.1.0
Summary: Plug-and-play authentication library for FastAPI — local + social OAuth2, JWT sessions, email verification, and more.
License: MIT
Keywords: fastapi,authentication,oauth2,jwt,social-auth
Author: officialalkenes
Author-email: officialalkenes@users.noreply.github.com
Requires-Python: >=3.11
Classifier: Development Status :: 3 - Alpha
Classifier: Framework :: FastAPI
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Internet :: WWW/HTTP :: Session
Classifier: Topic :: Security
Classifier: Typing :: Typed
Provides-Extra: postgres
Requires-Dist: aiosqlite (>=0.19.0)
Requires-Dist: alembic (>=1.13.0)
Requires-Dist: asyncpg (>=0.29.0) ; extra == "postgres"
Requires-Dist: bcrypt (>=4.0.0)
Requires-Dist: email-validator (>=2.1.0)
Requires-Dist: fastapi (>=0.110.0)
Requires-Dist: httpx (>=0.27.0)
Requires-Dist: pydantic (>=2.0.0)
Requires-Dist: pydantic-settings (>=2.0.0)
Requires-Dist: python-dotenv (>=1.0.0)
Requires-Dist: python-jose[cryptography] (>=3.3.0)
Requires-Dist: python-multipart (>=0.0.9)
Requires-Dist: sqlalchemy[asyncio] (>=2.0.0)
Project-URL: Bug Tracker, https://github.com/officialalkenes/pyallauth/issues
Project-URL: Changelog, https://github.com/officialalkenes/pyallauth/blob/main/CHANGELOG.md
Project-URL: Documentation, https://github.com/officialalkenes/pyallauth#readme
Project-URL: Homepage, https://github.com/officialalkenes/pyallauth
Project-URL: Repository, https://github.com/officialalkenes/pyallauth
Description-Content-Type: text/markdown

# pyallauth

[![PyPI version](https://img.shields.io/pypi/v/pyallauth.svg)](https://pypi.org/project/pyallauth/)
[![Tests](https://img.shields.io/github/actions/workflow/status/officialalkenes/pyallauth/test.yml?label=tests)](https://github.com/officialalkenes/pyallauth/actions)
[![Coverage](https://img.shields.io/codecov/c/github/officialalkenes/pyallauth)](https://codecov.io/gh/officialalkenes/pyallauth)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Python versions](https://img.shields.io/pypi/pyversions/pyallauth.svg)](https://pypi.org/project/pyallauth/)

**Plug-and-play authentication for FastAPI.** One import, one mount, full auth.

---

## What is pyallauth?

pyallauth is a batteries-included authentication library for FastAPI. If you've
ever copy-pasted JWT boilerplate between projects, stood up a Django-allauth
equivalent for FastAPI, or spent a weekend wiring together email verification,
OAuth, and token refresh — this library is for you.

Mount it once and you get:

- **Local auth** — register, login, logout, token refresh
- **Social OAuth2** — Google, GitHub, Facebook, Twitter/X
- **Email verification** — mandatory, optional, or disabled
- **Password reset** — single-use token flow with email
- **Account management** — profile, change email, change password, delete
- **Secure JWT sessions** — short-lived access + long-lived refresh with blacklisting
- **Async-first** — every DB call, email send, and OAuth exchange is awaitable

---

## Features

- Single router mount — full auth in < 5 lines
- Pluggable user model — extend the base or bring your own
- Adapter pattern — SQLAlchemy async out of the box; Tortoise-ORM ready
- Pydantic v2 schemas throughout
- bcrypt password hashing with passlib CryptContext
- PKCE support for Twitter/X OAuth2
- Email backend abstraction — SMTP or console (for development)
- Refresh token rotation and blacklisting
- Single-use tokens with UTC-aware expiry
- >85% test coverage

---

## Installation

```bash
# pip
pip install pyallauth

# With PostgreSQL support (asyncpg driver)
pip install "pyallauth[postgres]"

# Poetry
poetry add pyallauth
```

**Requirements**: Python 3.11+, FastAPI ≥ 0.110, SQLAlchemy ≥ 2.0.

---

## Quickstart

```python
from fastapi import FastAPI
from pyallauth import PyAllAuth

app = FastAPI()

auth = PyAllAuth(
    secret_key="your-secret-key",                           # Required
    database_url="sqlite+aiosqlite:///./app.db",            # SQLite for dev
    email_verification="mandatory",
    email_backend="console",                                # Prints to terminal
)

auth.register_exception_handlers(app)
app.include_router(auth.router, prefix="/auth")
```

That's it. Open `http://localhost:8000/docs` to see all 16 auth endpoints.

### Create database tables

```python
from pyallauth.models.sqlalchemy import create_tables, make_engine

@app.on_event("startup")
async def startup():
    engine = make_engine("sqlite+aiosqlite:///./app.db")
    await create_tables(engine)
    await engine.dispose()
```

In production, use Alembic migrations instead.

### Protecting your own endpoints

```python
from fastapi import Depends

@app.get("/dashboard")
async def dashboard(current_user = Depends(auth._get_current_user)):
    return {"email": current_user.email}
```

---

## Configuration Reference

All settings can be passed as constructor arguments to `PyAllAuth` or as
environment variables with the `PYALLAUTH_` prefix.

| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `secret_key` | `str` | **required** | JWT signing secret |
| `algorithm` | `str` | `"HS256"` | JWT algorithm |
| `access_token_expire` | `int` | `15` | Access token lifetime (minutes) |
| `refresh_token_expire` | `int` | `7` | Refresh token lifetime (days) |
| `email_verification` | `str` | `"mandatory"` | `"mandatory"`, `"optional"`, `"none"` |
| `email_backend` | `str` | `"console"` | `"console"` or `"smtp"` |
| `smtp_host` | `str` | `"localhost"` | SMTP server hostname |
| `smtp_port` | `int` | `587` | SMTP server port |
| `smtp_user` | `str` | `""` | SMTP username |
| `smtp_password` | `str` | `""` | SMTP password |
| `smtp_from_email` | `str` | `"noreply@example.com"` | From address |
| `smtp_tls` | `bool` | `True` | Use STARTTLS |
| `frontend_url` | `str` | `"http://localhost:3000"` | Base URL for email links |
| `social_providers` | `list[str]` | `[]` | Enabled providers |
| `google_client_id` | `str\|None` | `None` | Google OAuth2 client ID |
| `google_client_secret` | `str\|None` | `None` | Google OAuth2 client secret |
| `github_client_id` | `str\|None` | `None` | GitHub OAuth2 client ID |
| `github_client_secret` | `str\|None` | `None` | GitHub OAuth2 client secret |
| `facebook_client_id` | `str\|None` | `None` | Facebook app ID |
| `facebook_client_secret` | `str\|None` | `None` | Facebook app secret |
| `twitter_client_id` | `str\|None` | `None` | Twitter/X client ID |
| `twitter_client_secret` | `str\|None` | `None` | Twitter/X client secret |

---

## API Reference

All endpoints are mounted at the prefix you choose (`/auth` recommended).

### Local Authentication

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| `POST` | `/auth/register` | — | Register new account |
| `POST` | `/auth/login` | — | Login, receive JWT pair |
| `POST` | `/auth/logout` | — | Blacklist refresh token |
| `POST` | `/auth/token/refresh` | — | Exchange refresh for new access token |

**Register** `POST /auth/register`
```json
{
  "email": "user@example.com",
  "password": "StrongPass1!",
  "full_name": "Jane Doe"
}
```

**Login** `POST /auth/login`
```json
{
  "email": "user@example.com",
  "password": "StrongPass1!"
}
```
Response:
```json
{
  "access_token": "eyJ...",
  "refresh_token": "eyJ...",
  "token_type": "bearer",
  "expires_in": 900
}
```

### Email Verification

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| `GET` | `/auth/email/verify/{token}` | — | Verify email with token |
| `POST` | `/auth/email/resend` | Bearer | Resend verification email |

### Password Reset

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| `POST` | `/auth/password/reset` | — | Send reset email |
| `POST` | `/auth/password/reset/confirm` | — | Set new password with token |

**Reset request** `POST /auth/password/reset`
```json
{"email": "user@example.com"}
```

**Reset confirm** `POST /auth/password/reset/confirm`
```json
{"token": "abc123...", "new_password": "NewStrongPass1!"}
```

### Account Management

All endpoints require `Authorization: Bearer <access_token>`.

| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/auth/account/me` | Get profile |
| `PATCH` | `/auth/account/me` | Update profile |
| `POST` | `/auth/account/change-password` | Change password |
| `POST` | `/auth/account/change-email` | Change email |
| `DELETE` | `/auth/account/me` | Delete account |

### Social OAuth2

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| `GET` | `/auth/social/{provider}/login` | — | Get authorisation URL |
| `GET` | `/auth/social/{provider}/callback` | — | Handle callback, return JWT |
| `GET` | `/auth/social/accounts` | Bearer | List linked accounts |
| `DELETE` | `/auth/social/accounts/{id}` | Bearer | Disconnect account |

Supported `{provider}` values: `google`, `github`, `facebook`, `twitter`.

---

## Custom User Model

pyallauth's user table (`pyallauth_users`) covers the required fields.
To add custom fields, subclass `UserModel`:

```python
# myapp/models.py
from pyallauth.models.sqlalchemy import UserModel
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy import String

class User(UserModel):
    __tablename__ = "users"  # Override table name

    phone_number: Mapped[str | None] = mapped_column(String(20))
    avatar_url: Mapped[str | None] = mapped_column(String(512))
```

Then create tables using Alembic with `User` in the metadata.

---

## Custom Adapter Guide

To use a different ORM (Tortoise, Beanie, etc.):

```python
from pyallauth.models.base import AbstractUserAdapter, UserRecord

class MyAdapter(AbstractUserAdapter):
    async def get_user_by_email(self, email: str) -> UserRecord | None:
        ...  # implement all abstract methods

    # ... implement remaining 14 methods

# Pass to PyAllAuth
auth = PyAllAuth(
    secret_key="...",
    adapter=MyAdapter(),
)
```

See `pyallauth/models/base.py` for the full interface.

---

## Social Provider Setup

### Google

1. [Google Cloud Console](https://console.cloud.google.com/) → Credentials → OAuth 2.0 Client ID
2. Add `https://yourapp.com/auth/social/google/callback` to Authorised redirect URIs
3. Set `PYALLAUTH_GOOGLE_CLIENT_ID` and `PYALLAUTH_GOOGLE_CLIENT_SECRET`

```python
auth = PyAllAuth(
    secret_key="...",
    database_url="...",
    social_providers=["google"],
    google_client_id="your-client-id",
    google_client_secret="your-client-secret",
)
```

### GitHub

1. [GitHub Developer Settings](https://github.com/settings/developers) → New OAuth App
2. Set Authorization callback URL to `https://yourapp.com/auth/social/github/callback`
3. Set `PYALLAUTH_GITHUB_CLIENT_ID` and `PYALLAUTH_GITHUB_CLIENT_SECRET`

---

## How This Was Built

Read the full step-by-step implementation journal in [DEVLOG.md](./DEVLOG.md).
It covers every architectural decision, from the adapter pattern to token expiry
handling to why we use `hmac.compare_digest` for token comparisons.

---

## Roadmap

- [ ] Tortoise-ORM adapter
- [ ] Microsoft/Azure AD provider
- [ ] LinkedIn provider
- [ ] Apple Sign In provider
- [ ] Magic link (passwordless) authentication
- [ ] WebAuthn / passkey support
- [ ] Redis-based refresh token store (for high-throughput scenarios)
- [ ] Alembic migration scripts shipped with the package
- [ ] Pre-built Jinja2 email templates
- [ ] Rate limiting on auth endpoints

---

## Contributing

We welcome contributions of all sizes — bug fixes, new providers, new ORM
adapters, documentation improvements. See [CONTRIBUTING.md](./CONTRIBUTING.md)
for the local setup guide, branch naming convention, and PR checklist.

---

## License

MIT © 2026 officialalkenes. See [LICENSE](./LICENSE) for details.

