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.