Production-grade auth SDK for Indian apps — Email OTP, PIN, DPDP consent, device trust
Project description
BharatAuth
Production-grade auth SDK for Indian apps.
Email OTP · Password login · PIN app-lock · DPDP consent · Device trust matrix · Brute-force protection
Built from a real production auth system. Battle-tested on FastAPI + PostgreSQL.
Install
pip install bharatauth
Quick Start
Managed mode (new project — no existing user table)
from bharatauth import configure
configure(
mode="managed",
secret_key="change-me-in-production",
database_url="postgresql://user:pass@localhost/mydb",
smtp_host="smtp.yourprovider.com",
smtp_user="noreply@yourapp.com",
smtp_password="...",
)
BharatAuth creates ba_users, ba_accounts, ba_sessions, ba_otp_tokens, ba_consent_log, ba_ip_blocks and manages them entirely.
External mode (you already have a User model)
from bharatauth import configure
configure(
mode="external",
secret_key="change-me-in-production",
database_url="postgresql://user:pass@localhost/mydb",
get_user_by_email=lambda db, email: db.query(MyUser).filter_by(email=email).first(),
get_user_by_username=lambda db, u: db.query(MyUser).filter_by(username=u).first(),
get_user_id=lambda user: str(user.id),
get_user_email=lambda user: user.email,
get_user_display_name=lambda user: user.first_name,
)
BharatAuth stores sessions, OTP tokens, and DPDP consent logs in its own ba_* tables using your user's ID as an opaque string. Your user table is never touched.
Drop-in FastAPI Router
from fastapi import FastAPI
from bharatauth.fastapi_router import router as auth_router, bharatauth_exception_handler
from bharatauth.exceptions import BharatAuthError
app = FastAPI()
app.add_exception_handler(BharatAuthError, bharatauth_exception_handler)
app.include_router(auth_router, prefix="/auth", tags=["Auth"])
This mounts:
| Method | Path | Description |
|---|---|---|
| POST | /auth/login | Password login |
| POST | /auth/otp/request | Send OTP email |
| POST | /auth/otp/verify | Verify OTP, create session |
| POST | /auth/pin/set | Set/change PIN |
| POST | /auth/pin/verify | Verify PIN (app-lock) |
| POST | /auth/pin/reset/request | PIN reset email |
| POST | /auth/pin/reset/confirm | Confirm PIN reset |
| POST | /auth/refresh | Rotate refresh token |
| POST | /auth/logout | Revoke session |
| POST | /auth/logout-all | Revoke all sessions |
| GET | /auth/sessions | List active sessions |
| GET | /auth/me | Current user from token |
| GET | /auth/privacy/consents | DPDP consent dashboard |
| POST | /auth/privacy/consent/withdraw | Withdraw consent category |
| GET | /auth/privacy/export | DPDP Right of Access export |
Use Directly (without the router)
from bharatauth.login import login, refresh_session, logout
from bharatauth.otp import request_otp, verify_otp
from bharatauth.pin import set_pin, verify_pin
from bharatauth.dpdp import record_consent, get_consents, withdraw_consent, export_user_data
# Password login
result = login(db, identifier="user@example.com", password="secret",
device_fingerprint="sha256...", is_public_device=False)
# Email OTP
request_otp(db, identifier="user@example.com", device_fingerprint="sha256...")
verify_otp(db, identifier="user@example.com", otp_code="483920", device_fingerprint="sha256...")
# PIN
set_pin(db, user=user_obj, pin="4829")
verify_pin(db, user=user_obj, pin="4829")
# DPDP — record consent at registration
from bharatauth.dpdp import validate_registration_consent, record_bulk_consent
consents = [
{"category": "identity", "granted": True},
{"category": "contact", "granted": True},
{"category": "location", "granted": True},
{"category": "security", "granted": True},
{"category": "communications", "granted": True},
{"category": "analytics", "granted": True},
]
validate_registration_consent(consents) # raises ConsentRequiredError if any missing
record_bulk_consent(db, user_id=str(user.id), consents=consents, ip_address="...")
Security Model
Session Trust Matrix
| Scenario | TTL | Step-up Required |
|---|---|---|
| Own device, known | 30 days | None |
| Own device, OTP login | 30 days | None |
| New unrecognised device | 7 days | Before sensitive actions |
| Public / borrowed device | 2 hours | Every sensitive action |
Brute-Force Protection (two layers)
Layer 1 — Route-level (Redis): Per-IP request rate limits. Fails open if Redis is unavailable — auth correctness is maintained by Layer 2.
Layer 2 — Service-level (PostgreSQL): Per-account failure counting. Redis-independent.
| Tier | Trigger | Action |
|---|---|---|
| Soft lock | 5 failures | Locked 7 minutes (423) |
| Hard lock | 10 failures | Email verification required (423) |
| IP block | 3+ distinct accounts from same IP | IP blocked 24h (429) |
PIN Security
PIN lockout returns 401, not 423. An attacker with a stolen JWT receives no signal that lockout triggered — 401 is indistinguishable from wrong PIN.
DPDP Compliance (India)
BharatAuth implements the Digital Personal Data Protection Act 2023:
- Six consent categories: identity, contact, location, security, communications, analytics
- Immutable audit trail: consent records are never updated or deleted — new rows only
- Notice versioning: each consent row records the exact privacy notice version shown
- Right of withdrawal: as easy as granting (single POST, DPDP §6)
- Right of access:
export_user_data()returns all auth data BharatAuth holds - Configurable categories: override via
BharatAuthConfig.dpdp_categories
Configuration Reference
configure(
# Required
mode="managed", # "managed" | "external"
secret_key="...", # JWT signing secret
# Database (one of these is required)
database_url="postgresql://...",
db_session_factory=get_db, # your own SQLAlchemy session factory
# Token TTLs
access_token_expire_minutes=15,
refresh_token_expire_days=30,
new_device_token_expire_days=7,
public_device_token_expire_hours=2,
otp_expire_minutes=10,
# Email (SMTP)
smtp_host="smtp.example.com",
smtp_port=587,
smtp_user="noreply@example.com",
smtp_password="...",
smtp_from="noreply@example.com",
smtp_tls=True,
# Redis (optional — rate limiting only, fails open without it)
redis_url="redis://localhost:6379",
# JWT claims
jwt_issuer="your-app",
jwt_audience="your-app-client",
# DPDP
dpdp_notice_version="1.0.0",
dpdp_categories=["identity", "contact", "location",
"security", "communications", "analytics"],
# External mode only
get_user_by_email=...,
get_user_by_username=...,
get_user_id=...,
get_user_email=...,
get_user_display_name=..., # optional, for email templates
)
Custom Email Backend
Swap the SMTP backend with any provider (SES, SendGrid, etc.):
from bharatauth.email import set_email_backend
class MyEmailBackend:
def send(self, *, to, subject, body_text, body_html=None):
# your implementation
my_email_client.send(to=to, subject=subject, body=body_text)
set_email_backend(MyEmailBackend())
License
MIT — Copyright JamBuster Technologies Pvt Ltd
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file bharatauth-0.1.0.tar.gz.
File metadata
- Download URL: bharatauth-0.1.0.tar.gz
- Upload date:
- Size: 32.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3ca772475ec35b2fffc4a7df5c6e4db0b4ce52facb13746e1d46ab4305f95d94
|
|
| MD5 |
94eecbf3e91b19ede56ebce742910825
|
|
| BLAKE2b-256 |
f71d640e306d22b1c72575341df9aea92e05759a2c2207ff32acc214a471274c
|
File details
Details for the file bharatauth-0.1.0-py3-none-any.whl.
File metadata
- Download URL: bharatauth-0.1.0-py3-none-any.whl
- Upload date:
- Size: 37.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fc69c816268661b43345f5837a581ecf2e60228cb3ffb03bb40a1fec091cb9ea
|
|
| MD5 |
a1a68f06293233e22491802de4438eef
|
|
| BLAKE2b-256 |
e57d2f9716cef8035ce5eae5b801c0c098b65b0dc632f5f72d89bc05f8d0e931
|