# magic_link
[](https://pypi.org/project/magic-link/)
[](LICENSE)
[](https://github.com/h8v6/magic-link/actions/workflows/ci.yml)
[](#testing)
A modular, framework-agnostic engine for delivering passwordless authentication via secure magic links. The project follows a βminimal core + opt-in extrasβ philosophy so you can embed the library inside any Python stack without surrendering control of storage, email, or infrastructure.
> **Docs live in [`/docs`](docs/)**. This README highlights the essentials; detailed guides, recipes, and release instructions are all linked below.
---
## Table of Contents
- [Features](#features)
- [Requirements](#requirements)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Extras](#extras)
- [CLI Utilities](#cli-utilities)
- [Documentation](#documentation)
- [Testing](#testing)
- [Release Process](#release-process)
- [Contributing](#contributing)
- [License](#license)
## Features
- π **Secure token engine** β deterministic HMAC signing, hashing, TTL enforcement, property-based tests.
- ποΈ **Pluggable storage** β in-memory for dev, SQLAlchemy for RDBMS, Redis for low-latency workloads.
- βοΈ **Mailer registry** β SMTP implementation plus extensible template hooks.
- ποΈ **Service facade** β `MagicLinkService` coordinates issuance, verification, rate limiting, and single-use guarantees.
- π οΈ **CLI helpers** β `magic-link generate-config` and `magic-link test-email` streamline setup and diagnostics.
- π **Extensive documentation** β quickstarts, framework recipes (FastAPI, Flask), and in-depth guides for email, migrations, and security.
- β
**100% test coverage** β unit, integration, and property-based suites validated in CI against live PostgreSQL, Redis, and audo SMTP servers.
## Requirements
- Python 3.8+
- Optional services depending on extras:
- PostgreSQL (or any SQLAlchemy-supported database) for the SQL backend
- Redis 5+ for the Redis backend
- SMTP relay for outbound email
## Installation
Core library:
```bash
pip install magic-link
```
With common extras:
```bash
pip install "magic-link[sqlalchemy,redis,smtp]"
```
Add `cli` if you want the command-line utilities bundled:
```bash
pip install "magic-link[sqlalchemy,redis,smtp,cli]"
```
## Quick Start
1. Generate a configuration template:
```bash
magic-link generate-config -o .env
```
2. Fill in the environment variables (secret key, SMTP credentials, database connection, etc.).
3. Wire the library into your framework. For FastAPI:
```python
from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse
from magic_link import MagicLinkConfig, MagicLinkService
from magic_link.interfaces import MagicLinkMessage
from magic_link.mailer import create_mailer
from magic_link.storage.redis import RedisStorage
app = FastAPI()
config = MagicLinkConfig.from_env()
storage = RedisStorage.from_url("redis://localhost:6379/0") # see docs for helper
service = MagicLinkService(config=config, storage=storage)
mailer = create_mailer(config)
@app.post("/auth/magic-link")
async def issue(payload: dict[str, str]) -> JSONResponse:
email = payload.get("email")
if not email:
raise HTTPException(status_code=400, detail="Email is required")
service.enforce_rate_limit(email)
issued = service.issue_token(email)
link = f"{config.base_url}{config.login_path}?token={issued.token}"
mailer.send_magic_link(MagicLinkMessage(recipient=email, link=link, subject="Sign in", expires_at=issued.expires_at))
return JSONResponse({"status": "sent"})
```
4. Verify tokens using `service.verify_token(...)` in your callback endpoint.
For a complete, copy-pasteable example (including Flask), consult the [Quickstart guide](docs/quickstart.md) and [recipes](docs/recipes/).
## Extras
| Extra | Installs | Purpose |
|--------------|--------------------|--------------------------------------------------|
| `sqlalchemy` | `SQLAlchemy`, `psycopg[binary]` | Persistent token storage in relational DBs |
| `redis` | `redis`, `hiredis` | High-throughput storage + rate limiting |
| `smtp` | `email-validator` | SMTP mailer backend and email utilities |
| `cli` | `click` | Command-line helpers (`magic-link` CLI) |
## CLI Utilities
```bash
# Print an annotated configuration template
magic-link generate-config
# Send a test email using your configured mailer
magic-link test-email user@example.com
```
## Documentation
- [Overview & FastAPI quickstart](docs/README.md)
- [Step-by-step Quickstart](docs/quickstart.md)
- [Framework recipes](docs/recipes/) β Flask and more
- [Guides](docs/guides/) β email templates, database migrations, security considerations
- [Architecture](docs/architecture.md)
- [Release process](docs/release.md)
## Testing
```bash
pip install -e ".[dev,sqlalchemy,redis,smtp,cli]"
pytest --cov=magic_link
```
CI runs the full suite (unit, property-based, integration) against PostgreSQL, Redis, and a local SMTP server. Coverage must stay β₯95% (currently 100%).
## Release Process
1. Follow the checklist in [docs/release.md](docs/release.md) (trusted publisher + semver workflow).
2. Alpha / beta versions: bump `pyproject.toml`, update `CHANGELOG.md`, tag, and publish.
3. Official releases are cut via GitHub Releases, which triggers the trusted publisher workflow to upload builds to PyPI.
## Contributing
- Open an issue or draft PR for discussion.
- Ensure tests and linters pass (`pytest`, `ruff check`, `black --check`, `mypy`).
- Update documentation for user-facing changes.
## License
Distributed under the [MIT License](LICENSE).
Raw data
{
"_id": null,
"home_page": null,
"name": "magic-link",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": null,
"keywords": "authentication, magic-link, security, passwordless, python",
"author": "magic-link maintainers",
"author_email": null,
"download_url": "https://files.pythonhosted.org/packages/3a/08/99c55a4c7b85bcb251c72b93fa5d60bd93f073fc3f0e1377fbac2c57c7d1/magic_link-1.0.0.tar.gz",
"platform": null,
"description": "# magic_link\n\n[](https://pypi.org/project/magic-link/)\n[](LICENSE)\n[](https://github.com/h8v6/magic-link/actions/workflows/ci.yml)\n[](#testing)\n\nA modular, framework-agnostic engine for delivering passwordless authentication via secure magic links. The project follows a \u201cminimal core + opt-in extras\u201d philosophy so you can embed the library inside any Python stack without surrendering control of storage, email, or infrastructure.\n\n> **Docs live in [`/docs`](docs/)**. This README highlights the essentials; detailed guides, recipes, and release instructions are all linked below.\n\n---\n\n## Table of Contents\n\n- [Features](#features)\n- [Requirements](#requirements)\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Extras](#extras)\n- [CLI Utilities](#cli-utilities)\n- [Documentation](#documentation)\n- [Testing](#testing)\n- [Release Process](#release-process)\n- [Contributing](#contributing)\n- [License](#license)\n\n## Features\n\n- \ud83d\udd10 **Secure token engine** \u2013 deterministic HMAC signing, hashing, TTL enforcement, property-based tests.\n- \ud83d\uddc3\ufe0f **Pluggable storage** \u2013 in-memory for dev, SQLAlchemy for RDBMS, Redis for low-latency workloads.\n- \u2709\ufe0f **Mailer registry** \u2013 SMTP implementation plus extensible template hooks.\n- \ud83c\udf9b\ufe0f **Service facade** \u2013 `MagicLinkService` coordinates issuance, verification, rate limiting, and single-use guarantees.\n- \ud83d\udee0\ufe0f **CLI helpers** \u2013 `magic-link generate-config` and `magic-link test-email` streamline setup and diagnostics.\n- \ud83d\udcda **Extensive documentation** \u2013 quickstarts, framework recipes (FastAPI, Flask), and in-depth guides for email, migrations, and security.\n- \u2705 **100% test coverage** \u2013 unit, integration, and property-based suites validated in CI against live PostgreSQL, Redis, and audo SMTP servers.\n\n## Requirements\n\n- Python 3.8+\n- Optional services depending on extras:\n - PostgreSQL (or any SQLAlchemy-supported database) for the SQL backend\n - Redis 5+ for the Redis backend\n - SMTP relay for outbound email\n\n## Installation\n\nCore library:\n\n```bash\npip install magic-link\n```\n\nWith common extras:\n\n```bash\npip install \"magic-link[sqlalchemy,redis,smtp]\"\n```\n\nAdd `cli` if you want the command-line utilities bundled:\n\n```bash\npip install \"magic-link[sqlalchemy,redis,smtp,cli]\"\n```\n\n## Quick Start\n\n1. Generate a configuration template:\n ```bash\n magic-link generate-config -o .env\n ```\n2. Fill in the environment variables (secret key, SMTP credentials, database connection, etc.).\n3. Wire the library into your framework. For FastAPI:\n\n ```python\n from fastapi import FastAPI, HTTPException\n from fastapi.responses import JSONResponse\n\n from magic_link import MagicLinkConfig, MagicLinkService\n from magic_link.interfaces import MagicLinkMessage\n from magic_link.mailer import create_mailer\n from magic_link.storage.redis import RedisStorage\n\n app = FastAPI()\n config = MagicLinkConfig.from_env()\n storage = RedisStorage.from_url(\"redis://localhost:6379/0\") # see docs for helper\n service = MagicLinkService(config=config, storage=storage)\n mailer = create_mailer(config)\n\n @app.post(\"/auth/magic-link\")\n async def issue(payload: dict[str, str]) -> JSONResponse:\n email = payload.get(\"email\")\n if not email:\n raise HTTPException(status_code=400, detail=\"Email is required\")\n service.enforce_rate_limit(email)\n issued = service.issue_token(email)\n link = f\"{config.base_url}{config.login_path}?token={issued.token}\"\n mailer.send_magic_link(MagicLinkMessage(recipient=email, link=link, subject=\"Sign in\", expires_at=issued.expires_at))\n return JSONResponse({\"status\": \"sent\"})\n ```\n\n4. Verify tokens using `service.verify_token(...)` in your callback endpoint.\n\nFor a complete, copy-pasteable example (including Flask), consult the [Quickstart guide](docs/quickstart.md) and [recipes](docs/recipes/).\n\n## Extras\n\n| Extra | Installs | Purpose |\n|--------------|--------------------|--------------------------------------------------|\n| `sqlalchemy` | `SQLAlchemy`, `psycopg[binary]` | Persistent token storage in relational DBs |\n| `redis` | `redis`, `hiredis` | High-throughput storage + rate limiting |\n| `smtp` | `email-validator` | SMTP mailer backend and email utilities |\n| `cli` | `click` | Command-line helpers (`magic-link` CLI) |\n\n## CLI Utilities\n\n```bash\n# Print an annotated configuration template\nmagic-link generate-config\n\n# Send a test email using your configured mailer\nmagic-link test-email user@example.com\n```\n\n## Documentation\n\n- [Overview & FastAPI quickstart](docs/README.md)\n- [Step-by-step Quickstart](docs/quickstart.md)\n- [Framework recipes](docs/recipes/) \u2013 Flask and more\n- [Guides](docs/guides/) \u2013 email templates, database migrations, security considerations\n- [Architecture](docs/architecture.md)\n- [Release process](docs/release.md)\n\n## Testing\n\n```bash\npip install -e \".[dev,sqlalchemy,redis,smtp,cli]\"\npytest --cov=magic_link\n```\n\nCI runs the full suite (unit, property-based, integration) against PostgreSQL, Redis, and a local SMTP server. Coverage must stay \u226595% (currently 100%).\n\n## Release Process\n\n1. Follow the checklist in [docs/release.md](docs/release.md) (trusted publisher + semver workflow).\n2. Alpha / beta versions: bump `pyproject.toml`, update `CHANGELOG.md`, tag, and publish.\n3. Official releases are cut via GitHub Releases, which triggers the trusted publisher workflow to upload builds to PyPI.\n\n## Contributing\n\n- Open an issue or draft PR for discussion.\n- Ensure tests and linters pass (`pytest`, `ruff check`, `black --check`, `mypy`).\n- Update documentation for user-facing changes.\n\n## License\n\nDistributed under the [MIT License](LICENSE).\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Modular, framework-agnostic passwordless authentication engine.",
"version": "1.0.0",
"project_urls": {
"Documentation": "https://github.com/h8v6/magic-link/tree/main/docs",
"Homepage": "https://github.com/h8v6/magic-link",
"Issues": "https://github.com/h8v6/magic-link/issues",
"Repository": "https://github.com/h8v6/magic-link"
},
"split_keywords": [
"authentication",
" magic-link",
" security",
" passwordless",
" python"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "48920f00e86bb7d621c22375ae4644e7fb51b477a464c9450e105f0c1851536c",
"md5": "ea1e75e3b29ac320d211643ff4d890a1",
"sha256": "42dcf7a2f92cac1f2cd196505afcdb10cc1f518a7d29511cea29e51f03a2b7d9"
},
"downloads": -1,
"filename": "magic_link-1.0.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "ea1e75e3b29ac320d211643ff4d890a1",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 22072,
"upload_time": "2025-10-19T18:17:26",
"upload_time_iso_8601": "2025-10-19T18:17:26.628447Z",
"url": "https://files.pythonhosted.org/packages/48/92/0f00e86bb7d621c22375ae4644e7fb51b477a464c9450e105f0c1851536c/magic_link-1.0.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "3a0899c55a4c7b85bcb251c72b93fa5d60bd93f073fc3f0e1377fbac2c57c7d1",
"md5": "0a4be4ddca3009566627e05520ac42ea",
"sha256": "2236eed2e8d3952df1834abee6338d8c85f64b42b90129df47edf4aa6eeb2b19"
},
"downloads": -1,
"filename": "magic_link-1.0.0.tar.gz",
"has_sig": false,
"md5_digest": "0a4be4ddca3009566627e05520ac42ea",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 24942,
"upload_time": "2025-10-19T18:17:27",
"upload_time_iso_8601": "2025-10-19T18:17:27.927224Z",
"url": "https://files.pythonhosted.org/packages/3a/08/99c55a4c7b85bcb251c72b93fa5d60bd93f073fc3f0e1377fbac2c57c7d1/magic_link-1.0.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-10-19 18:17:27",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "h8v6",
"github_project": "magic-link",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "magic-link"
}