Haze - Lightning-Fast Magic Link Authentication
Haze is a high-performance, easy-to-use Magic Link Authentication service for Python applications. Generate secure authentication links that work across devices with minimal setup.
Features
- ⚡ Fast & Efficient - Optimized core with minimal overhead
- 🔒 Ultra Secure - Modern cryptography with JWT
- 🔧 Highly Configurable - Like Neovim, but for authentication
- 🧩 Zero Daemon - No background processes required
- 📦 Minimal Dependencies - Lightweight core with optional extras
- 📱 Cross-Device Auth - Click on phone, authenticate on desktop
- 💾 Pluggable Storage - Use any database system
- 🦄 Modern Defaults - NanoID, JWT, MsgPack by default
Installation
Since Haze is now available on PYPI, you can use any package manager you want.
pip install haze-auth[full]
# Basic installation (install jwt later, with different package version)
pip install git+https://github.com/itsmeadarsh2008/haze.git@main
# With JWT support (Recommended)
pip install "haze[jwt] @ git+https://github.com/itsmeadarsh2008/haze.git@main"
# With all optional dependencies
pip install "haze [full] @ git+https://github.com/itsmeadarsh2008/haze.git@main"
You can also specify individual extra dependencies:
# Pick and choose what you need
pip install "haze[<optional deps>] git+https://github.com/itsmeadarsh2008/haze.git"
Quick Start
import haze
import secrets
# Configure Haze
haze.use(
base_url="https://myapp.com",
magic_link_path="/auth/verify",
secret_key=secrets.token_urlsafe(32)
)
# Simple in-memory storage for demo purposes
token_store = {}
# Define storage handler
@haze.storage
def store_token(token_id, data=None):
if data is None:
return token_store.get(token_id)
token_store[token_id] = data
return data
# Generate a magic link for a user
link = haze.generate(
user_id="user123",
metadata={"name": "John Doe", "email": "john@example.com"}
)
print(f"Magic Link: {link}")
# Verify the magic link
# This is typically done in your web endpoint
@app.route("/auth/verify")
def verify_link():
token_id = request.args.get("token_id")
signature = request.args.get("signature")
try:
user_data = haze.verify(token_id, signature)
# Authentication successful
# Set session, JWT, etc.
return {"success": True, "user": user_data}
except Exception as e:
return {"success": False, "error": str(e)}
Advanced Usage
Custom Configuration
haze.use(
# Base settings
base_url="https://myapp.com",
magic_link_path="/auth/magic",
link_expiry=3600, # 1 hour
allow_reuse=False, # One-time use by default
# Token settings
token_provider="jwt",
jwt_algorithm="HS256", # or RS256, ES256
# ID generation
id_generator="nanoid", # or "uuid"
nanoid_size=21,
# Format settings
serialization_format="msgpack" # or "json"
)
Using with JWT
import secrets
# Generate a secure key
secret_key = secrets.token_urlsafe(32)
# Configure Haze to use JWT with HMAC
haze.use(
token_provider="jwt",
jwt_algorithm="HS256",
secret_key=secret_key
)
Using with Asymmetric Keys (JWT)
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
# Generate key pair
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048
)
public_key = private_key.public_key()
# Configure Haze
haze.use(
token_provider="jwt",
jwt_algorithm="RS256",
private_key=private_key,
public_key=public_key
)
Database Integration Examples
With SQLAlchemy
from sqlalchemy import create_engine, Column, String, Integer, Boolean, JSON, Text
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
# Setup database
Base = declarative_base()
engine = create_engine("sqlite:///haze_tokens.db")
Session = sessionmaker(bind=engine)
class Token(Base):
__tablename__ = "tokens"
token_id = Column(String, primary_key=True)
user_id = Column(String, nullable=False)
exp = Column(Integer, nullable=False)
created_at = Column(Integer, nullable=False)
metadata = Column(JSON, nullable=True)
consumed = Column(Boolean, default=False)
Base.metadata.create_all(engine)
# Setup Haze storage handler
@haze.storage
def store_token(token_id, data=None):
session = Session()
try:
if data is None:
# Retrieve token
token = session.query(Token).filter_by(token_id=token_id).first()
if not token:
return None
return {
"user_id": token.user_id,
"exp": token.exp,
"created_at": token.created_at,
"metadata": token.metadata,
"consumed": token.consumed
}
else:
# Create or update token
token = session.query(Token).filter_by(token_id=token_id).first()
if token:
# Update existing token
token.user_id = data["user_id"]
token.exp = data["exp"]
token.created_at = data.get("created_at")
token.metadata = data.get("metadata")
token.consumed = data.get("consumed", False)
else:
# Create new token
token = Token(
token_id=token_id,
user_id=data["user_id"],
exp=data["exp"],
created_at=data.get("created_at"),
metadata=data.get("metadata"),
consumed=data.get("consumed", False)
)
session.add(token)
session.commit()
return data
finally:
session.close()
With Redis
import redis
import json
import time
# Setup Redis connection
r = redis.Redis(host='localhost', port=6379, db=0)
@haze.storage
def store_token(token_id, data=None):
key = f"haze:token:{token_id}"
if data is None:
# Retrieve token
token_data = r.get(key)
if not token_data:
return None
return json.loads(token_data)
else:
# Store token with expiration
ttl = data["exp"] - int(time.time())
r.setex(key, ttl, json.dumps(data))
return data
Event Handlers
Haze provides hooks for various authentication events:
# Called when a link is verified
@haze.verification
def on_verification(user_id, token_data):
print(f"User {user_id} verified with token: {token_data['jti']}")
# Update last login time, etc.
# Called when a magic link is clicked
@haze.onclick
def on_link_clicked(user_id, user_data):
print(f"User {user_id} clicked magic link")
# Track analytics, etc.
Using with Popular Web Frameworks
Flask Example
from flask import Flask, request, redirect, session
import haze
import secrets
app = Flask(__name__)
app.secret_key = secrets.token_urlsafe(32)
# Configure Haze
haze.use(
base_url="http://localhost:5000", # For local development
magic_link_path="/auth/verify",
secret_key=app.secret_key
)
# Simple in-memory storage for demo purposes
token_store = {}
@haze.storage
def store_token(token_id, data=None):
if data is None:
return token_store.get(token_id)
token_store[token_id] = data
return data
@app.route("/login", methods=["POST"])
def login():
email = request.form.get("email")
if not email:
return {"error": "Email required"}, 400
# Generate magic link
link = haze.generate(
user_id=email,
metadata={"email": email}
)
# In a real app, send this link via email
# For demo, we'll just return it
return {"link": link}
@app.route("/auth/verify")
def verify():
token_id = request.args.get("token_id")
signature = request.args.get("signature")
try:
user_data = haze.verify(token_id, signature)
# Set session
session["user_id"] = user_data["user_id"]
session["authenticated"] = True
# Redirect to dashboard
return redirect("/dashboard")
except Exception as e:
return {"error": str(e)}, 400
@app.route("/dashboard")
def dashboard():
if not session.get("authenticated"):
return redirect("/login")
return f"Welcome, {session.get('user_id')}!"
FastAPI Example
from fastapi import FastAPI, Depends, HTTPException, Request, Response
from fastapi.responses import RedirectResponse
from pydantic import BaseModel, EmailStr
import secrets
import haze
app = FastAPI()
# Configure Haze
haze.use(
base_url="http://localhost:8000", # For local development
magic_link_path="/auth/verify",
secret_key=secrets.token_urlsafe(32)
)
# Simple in-memory storage
token_store = {}
@haze.storage
def store_token(token_id, data=None):
if data is None:
return token_store.get(token_id)
token_store[token_id] = data
return data
class LoginRequest(BaseModel):
email: EmailStr
@app.post("/login")
async def login(request: LoginRequest):
# Generate magic link
link = haze.generate(
user_id=request.email,
metadata={"email": request.email}
)
# In a real app, send this link via email
return {"link": link}
@app.get("/auth/verify")
async def verify(token_id: str, signature: str, response: Response):
try:
user_data = haze.verify(token_id, signature)
# Set cookie for authentication
response.set_cookie(
key="session_token",
value=user_data["user_id"],
httponly=True,
secure=False, # Set to True in production with HTTPS
samesite="lax"
)
return RedirectResponse(url="/dashboard")
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
@app.get("/dashboard")
async def dashboard(request: Request):
session_token = request.cookies.get("session_token")
if not session_token:
return RedirectResponse(url="/login")
return {"message": f"Welcome, {session_token}!"}
Security Best Practices
- Always use HTTPS for production environments
- Set appropriate token expiry times - shorter is better
- Rotate your secret keys periodically
- Use asymmetric cryptography (JWT with RSA/ECDSA) for increased security
- Implement rate limiting to prevent brute force attacks
- Store tokens securely in a database with proper encryption
- Enable one-time use for magic links by setting
allow_reuse=False
Troubleshooting
Common Issues
"ModuleNotFoundError" for optional dependencies
pip install "haze[full] @ git+https://github.com/itsmeadarsh2008/haze.git"
"ConfigurationError: secret_key must be set"
Ensure you've set a secure secret key with haze.use(secret_key=...).
"ValidationError: Token expired"
The magic link has expired. Generate a new one or increase the link_expiry setting.
"ValidationError: Token not found"
The token doesn't exist in storage. Check your storage handler implementation.
"ValidationError: Invalid signature"
The signature verification failed. This could indicate a tampered link or configuration issues.
Release files for haze-auth 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| haze_auth-0.1.2.tar.gz | 15.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| haze_auth-0.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 34.5 kB
Release files / haze_auth-0.1.2.tar.gz
| Download URL | haze_auth-0.1.2.tar.gz |
|---|---|
| Size | 15.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c885e7cad8b6f6fa270b5091b13a2d5002e31f7d84539e35496e675653749fe5
|
|
BLAKE2b-256 checksum How to use checksums |
88caa9855e541cd8d761effdc9f8b959d82e76a35f18ef47875ce1de9ccf5b66
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.12.9
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 18, 2025.
Transparency logRelease files / haze_auth-0.1.2-py3-none-any.whl
| Download URL | haze_auth-0.1.2-py3-none-any.whl |
|---|---|
| Size | 18.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
54b3ead8d0d55e9cec949ba883ce0eacec95a4c988bda6ac06fe37f209b40057
|
|
BLAKE2b-256 checksum How to use checksums |
8f1e3e57479c57d90aa61389eb07a1ca6fa74d2643193beb8a016639c27e4237
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.12.9
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 18, 2025.
Transparency log