Language: 🇬🇧 English · 🇮🇷 فارسی
A production-grade, passwordless authentication platform built on Django REST Framework.
Users authenticate via a 6-digit OTP delivered over SMS (Kavenegar) or printed to the console in development. On success the system issues a short-lived JWT Access Token (15 min) and a rotating, blacklisting Refresh Token (7 d) — each cryptographically bound to a tracked Session (device fingerprint, IP address, User-Agent). Every request is validated against the session state, and all sensitive operations are protected by multi-layer rate limiting, asynchronous audit logging, token-reuse detection, and IDOR prevention.
| # | Feature |
|---|---|
| 1 | Passwordless OTP login / registration |
| 2 | JWT Access (15 min) + Refresh (7 days) |
| 3 | Refresh Token Rotation + Blacklist |
| 4 | Token Reuse Detection → revokes all sessions |
| 5 | Session management (list / revoke / revoke-all) |
| 6 | Device fingerprint + IP + User-Agent tracking |
| 7 | Multi-layer Rate Limiting + automatic IP blocking |
| 8 | Async Audit Log + Security Events |
| 9 | Cookie-based auth (HttpOnly refresh token) |
| 10 | IDOR Prevention on sessions & profile |
| 11 | drf-yasg Swagger / ReDoc documentation |
| 12 | 140 automated tests — 100% passing |
┌─────────────┐ HTTP ┌────────────┐ ┌─────────────────┐
│ Client / │ ──────► │ Nginx :80 │ ─────► │ Gunicorn :8000 │
│ Postman / │ └────────────┘ │ (Django + DRF) │
│ Swagger │ └───────┬─────────┘
└─────────────┘ │
┌─────────────────────────────────┼─────────────────────────┐
│ │ │
┌─────▼──────┐ Cache/OTP ┌──────▼───────┐ ┌───────▼───────┐
│ Redis │ ◄───────────────► │ Celery │ │ SQLite │
│ Broker + │ ── Broker ──────► │ Worker │ │ (WAL mode) │
│ Cache │ └──────────────┘ └───────────────┘
└────────────┘
Docker services: backend · celery_worker · redis · nginx · locust master/worker · smtp4dev
The OTP is never stored in plain text. An HMAC-SHA256 hash — keyed by
SECRET_KEY + phone_number + purpose — is written to Redis with a 5-minute TTL.
A maximum of 3 verification attempts is allowed; exceeding this invalidates the OTP.
On every refresh the previous token is blacklisted and a fresh pair is issued (Rotation).
Any attempt to reuse a blacklisted token raises a SecurityEvent and revokes all of the
user's sessions and outstanding tokens.
Each token carries a session_id. The custom CustomJWTAuthentication backend verifies on
every request that the session exists, is not revoked, and is not expired.
| Scope | Limit |
|---|---|
| OTP request / IP | 3 / 60s |
| OTP request / phone | 5 / 600s |
| Resend cooldown | 1 / 120s |
| OTP verify / phone | 5 / 300s |
| Failed verify → IP block | 5 → 15 min |
refresh_token is set as HttpOnly + SameSite=Lax (XSS/CSRF mitigation). access_token is
readable for SPA consumption. Both are cleared on logout.
- IDOR prevention on sessions & profile endpoints
- Asynchronous audit log & security events via Celery
- SQLite WAL mode for concurrent writes
- CORS
allow-credentialsconfigured - Strict input validation via DRF serializers
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /api/v1/accounts/otp/request/ |
✗ | Request OTP |
| POST | /api/v1/accounts/otp/verify/ |
✗ | Verify OTP → tokens + cookies |
| POST | /api/v1/accounts/refresh/ |
✗ | Refresh token pair |
| POST | /api/v1/accounts/logout/ |
✓ | Logout + revoke |
| GET/PUT/PATCH | /api/v1/accounts/profile/ |
✓ | Profile CRUD |
| GET | /api/v1/accounts/sessions/ |
✓ | List active sessions |
| DELETE | /api/v1/accounts/sessions/{id}/ |
✓ | Revoke a session |
| POST | /api/v1/accounts/sessions/revoke-all/ |
✓ | Revoke all sessions |
📚 Interactive docs: http://localhost/swagger/ · http://localhost/redoc/
[1] POST /otp/request/ ──► rate-limit ──► generate OTP ──► hash → Redis ──► SMS/Console
[2] POST /otp/verify/ ──► verify hash ──► create user+session ──► issue JWT pair ──► set cookies
[3] GET /profile/ ──► CustomJWTAuthentication ──► validate session ──► 200 / 401
[4] POST /refresh/ ──► blacklist old ──► issue new pair (rotation)
[5] POST /logout/ ──► blacklist + revoke session ──► clear cookies
A few previews below — see the full gallery in
screenshots/.
➡️ Full gallery (15 screenshots): screenshots/README.md
✅ 140 passed, 0 failed — in 9.29s
=================================================== test session starts ===================================================
platform linux -- Python 3.12.11, pytest-8.2.2, pluggy-1.6.0
django: version: 5.2.17, settings: core.settings.development (from ini)
plugins: django-4.8.0, Faker-33.3.1
collected 140 items
accounts/tests/test_api/test_auth_endpoints.py::TestOTPRequest::test_request_otp_success PASSED
accounts/tests/test_api/test_auth_endpoints.py::TestOTPVerify::test_verify_otp_success PASSED
accounts/tests/test_api/test_auth_endpoints.py::TestTokenRefresh::test_refresh_reuse_detection PASSED
accounts/tests/test_security/test_token_reuse.py::TestIDORPrevention::test_user_profile_isolation PASSED
... (136 more)
=================================================== 140 passed in 9.29s ==================================================
| Module | Tests | Status |
|---|---|---|
| API — Authentication | 31 | ✅ |
| API — Profile & Sessions | 18 | ✅ |
| Security — Rate Limiting | 15 | ✅ |
| Security — Token Reuse | 16 | ✅ |
| Services | 29 | ✅ |
| Models | 17 | ✅ |
| Managers | 14 | ✅ |
| Total | 140 | ✅ 100% |
📄 Full breakdown: TEST_RESULTS.md
# 1. Configure
cp .env.example .env # set SECRET_KEY, REDIS_HOST=redis, ...
# 2. Build & run
docker compose up -d --build
# 3. Open docs
# Swagger → http://localhost/swagger/
# ReDoc → http://localhost/redoc/
# 4. Run tests
docker compose exec backend pytest accounts/tests/ -v| Variable | Description |
|---|---|
SECRET_KEY |
Django secret key |
DEBUG |
Debug mode |
REDIS_HOST / REDIS_PORT |
Redis address |
CELERY_BROKER_URL |
Celery broker URL |
KAVENEGAR_API_KEY |
SMS provider key |
CORS_ALLOWED_ORIGINS |
Allowed origins |
django-secure-auth/
├── core/ # Settings, URLs, Celery, WSGI/ASGI
├── accounts/
│ ├── models.py # User, Profile, Session, OTPRecord, AuditLog, SecurityEvent
│ ├── managers.py # UserManager (phone normalization)
│ ├── auth.py # CustomJWTAuthentication (Header + Cookie + Session)
│ ├── services/ # otp.py, auth.py, security.py, sms.py
│ ├── tasks.py # Celery tasks
│ ├── api/v1/ # views / serializers / urls
│ └── tests/ # 140 tests
├── docker/ # Dockerfile, entrypoint, nginx
├── locust/ # Load testing
├── screenshots/ # Postman / Swagger / Admin evidence
├── docker-compose.yml
├── README.md
└── TEST_RESULTS.md
| Tool | Purpose |
|---|---|
pytest + pytest-django |
Automated testing |
ruff |
Linting |
black |
Formatting |
bandit |
Security scanning |
locust |
Load testing |
Moein Rezaie — 📧 moeinrezaie516@gmail.com
زبان: 🇬🇧 English · 🇮🇷 فارسی
یک پلتفرم احراز هویت بدون رمز عبور با کیفیت تولید، ساختهشده روی Django REST Framework.
کاربران با یک کد OTP ششرقمی که از طریق پیامک (کاوهنگار) ارسال یا در محیط توسعه در کنسول چاپ میشود، احراز هویت میشوند. پس از موفقیت، سیستم یک توکن دسترسی کوتاهمدت (۱۵ دقیقه) و یک توکن نوسازی چرخشی با لیستسیاه (۷ روز) صادر میکند — که هر دو بهصورت رمزنگارانه به یک نشست ردیابیشده (اثر انگشت دستگاه، آدرس IP، User-Agent) متصلاند. هر درخواست در برابر وضعیت نشست اعتبارسنجی میشود و همه عملیاتهای حساس با محدودسازی نرخ چندلایه، لاگ حسابرسی ناهمگام، تشخیص استفاده مجدد توکن و جلوگیری از IDOR محافظت میشوند.
| # | ویژگی |
|---|---|
| ۱ | ورود / ثبتنام بدون رمز عبور با OTP |
| ۲ | توکن دسترسی (۱۵ دقیقه) + توکن نوسازی (۷ روز) |
| ۳ | چرخش توکن نوسازی + لیست سیاه |
| ۴ | تشخیص استفاده مجدد توکن → ابطال همه نشستها |
| ۵ | مدیریت نشستها (لیست / ابطال / ابطال همه) |
| ۶ | ردیابی اثر انگشت دستگاه + IP + User-Agent |
| ۷ | محدودسازی نرخ چندلایه + مسدودسازی خودکار IP |
| ۸ | لاگ حسابرسی + رویدادهای امنیتی ناهمگام |
| ۹ | احراز هویت کوکیمحور (توکن نوسازی HttpOnly) |
| ۱۰ | جلوگیری از IDOR در نشستها و پروفایل |
| ۱۱ | مستندات Swagger / ReDoc با drf-yasg |
| ۱۲ | ۱۴۰ تست خودکار — ۱۰۰٪ موفق |
┌─────────────┐ HTTP ┌────────────┐ ┌─────────────────┐
│ کلاینت / │ ──────► │ Nginx :80 │ ─────► │ Gunicorn :8000 │
│ Postman / │ └────────────┘ │ (Django + DRF) │
│ Swagger │ └───────┬─────────┘
└─────────────┘ │
┌─────────────────────────────────┼─────────────────────────┐
│ │ │
┌─────▼──────┐ Cache/OTP ┌──────▼───────┐ ┌───────▼───────┐
│ Redis │ ◄───────────────► │ Celery │ │ SQLite │
│ Broker + │ ── Broker ──────► │ Worker │ │ (حالت WAL) │
│ Cache │ └──────────────┘ └───────────────┘
└────────────┘
سرویسهای Docker: backend · celery_worker · redis · nginx · locust master/worker · smtp4dev
کد OTP هرگز بهصورت متن ساده ذخیره نمیشود. یک هش HMAC-SHA256 — با کلید
SECRET_KEY + شماره تلفن + هدف — با انقضای ۵ دقیقه در Redis نوشته میشود.
حداکثر ۳ تلاش تأیید مجاز است؛ با عبور از این حد، OTP باطل میشود.
در هر نوسازی، توکن قبلی لیستسیاه و یک جفت جدید صادر میشود (چرخش).
هر تلاش برای استفاده مجدد از توکن لیستسیاه، یک SecurityEvent ثبت کرده و
همه نشستها و توکنهای فعال کاربر را باطل میکند.
هر توکن یک session_id حمل میکند. بکاند سفارشی CustomJWTAuthentication در
هر درخواست تأیید میکند که نشست وجود دارد، باطل نشده و منقضی نشده است.
| محدوده | محدودیت |
|---|---|
| درخواست OTP / IP | ۳ در ۶۰ ثانیه |
| درخواست OTP / شماره | ۵ در ۱۰ دقیقه |
| بازارسال | ۱ در ۲ دقیقه |
| تأیید OTP / شماره | ۵ در ۵ دقیقه |
| تأیید ناموفق → مسدودی IP | ۵ → ۱۵ دقیقه |
کوکی refresh_token با HttpOnly + SameSite=Lax تنظیم میشود (کاهش XSS/CSRF).
کوکی access_token برای مصرف SPA قابل خواندن است. هر دو در خروج پاک میشوند.
- جلوگیری از IDOR در نقاط پایانی نشستها و پروفایل
- لاگ حسابرسی و رویدادهای امنیتی ناهمگام با Celery
- حالت WAL در SQLite برای نوشتارهای همزمان
- پیکربندی CORS با
allow-credentials - اعتبارسنجی سختگیرانه ورودی با سریالایزرهای DRF
| متد | مسیر | احراز هویت | توضیح |
|---|---|---|---|
| POST | /api/v1/accounts/otp/request/ |
✗ | درخواست OTP |
| POST | /api/v1/accounts/otp/verify/ |
✗ | تأیید OTP → توکن + کوکی |
| POST | /api/v1/accounts/refresh/ |
✗ | نوسازی جفت توکن |
| POST | /api/v1/accounts/logout/ |
✓ | خروج + ابطال |
| GET/PUT/PATCH | /api/v1/accounts/profile/ |
✓ | عملیات پروفایل |
| GET | /api/v1/accounts/sessions/ |
✓ | لیست نشستهای فعال |
| DELETE | /api/v1/accounts/sessions/{id}/ |
✓ | ابطال یک نشست |
| POST | /api/v1/accounts/sessions/revoke-all/ |
✓ | ابطال همه نشستها |
📚 مستندات تعاملی: http://localhost/swagger/ · http://localhost/redoc/
[۱] POST /otp/request/ ──► محدودیت نرخ ──► تولید OTP ──► هش → Redis ──► پیامک/کنسول
[۲] POST /otp/verify/ ──► تأیید هش ──► ساخت کاربر+نشست ──► صدور جفت JWT ──► تنظیم کوکیها
[۳] GET /profile/ ──► CustomJWTAuthentication ──► اعتبارسنجی نشست ──► 200 / 401
[۴] POST /refresh/ ──► لیستسیاه قبلی ──► صدور جفت جدید (چرخش)
[۵] POST /logout/ ──► لیستسیاه + ابطال نشست ──► پاکسازی کوکیها
چند پیشنمایش در ادامه — گالری کامل در
screenshots/.
➡️ گالری کامل (۱۵ تصویر): screenshots/README.md
✅ ۱۴۰ موفق، ۰ ناموفق — در ۹.۲۹ ثانیه
=================================================== test session starts ===================================================
platform linux -- Python 3.12.11, pytest-8.2.2, pluggy-1.6.0
django: version: 5.2.17, settings: core.settings.development (from ini)
plugins: django-4.8.0, Faker-33.3.1
collected 140 items
accounts/tests/test_api/test_auth_endpoints.py::TestOTPRequest::test_request_otp_success PASSED
accounts/tests/test_api/test_auth_endpoints.py::TestOTPVerify::test_verify_otp_success PASSED
accounts/tests/test_api/test_auth_endpoints.py::TestTokenRefresh::test_refresh_reuse_detection PASSED
accounts/tests/test_security/test_token_reuse.py::TestIDORPrevention::test_user_profile_isolation PASSED
... (136 more)
=================================================== 140 passed in 9.29s ==================================================
| ماژول | تعداد تست | وضعیت |
|---|---|---|
| API — احراز هویت | ۳۱ | ✅ |
| API — پروفایل و نشستها | ۱۸ | ✅ |
| امنیت — محدودیت نرخ | ۱۵ | ✅ |
| امنیت — استفاده مجدد توکن | ۱۶ | ✅ |
| سرویسها | ۲۹ | ✅ |
| مدلها | ۱۷ | ✅ |
| منیجرها | ۱۴ | ✅ |
| مجموع | ۱۴۰ | ✅ ۱۰۰٪ |
📄 ریز کامل نتایج: TEST_RESULTS.md
# ۱. پیکربندی
cp .env.example .env # تنظیم SECRET_KEY, REDIS_HOST=redis, ...
# ۲. ساخت و اجرا
docker compose up -d --build
# ۳. باز کردن مستندات
# Swagger → http://localhost/swagger/
# ReDoc → http://localhost/redoc/
# ۴. اجرای تستها
docker compose exec backend pytest accounts/tests/ -v| متغیر | توضیح |
|---|---|
SECRET_KEY |
کلید محرمانه جنگو |
DEBUG |
حالت اشکالزدایی |
REDIS_HOST / REDIS_PORT |
آدرس Redis |
CELERY_BROKER_URL |
آدرس بروکر Celery |
KAVENEGAR_API_KEY |
کلید ارائهدهنده پیامک |
CORS_ALLOWED_ORIGINS |
دامنههای مجاز |
django-secure-auth/
├── core/ # تنظیمات، URLها، Celery، WSGI/ASGI
├── accounts/
│ ├── models.py # User, Profile, Session, OTPRecord, AuditLog, SecurityEvent
│ ├── managers.py # UserManager (نرمالسازی شماره تلفن)
│ ├── auth.py # CustomJWTAuthentication (Header + Cookie + Session)
│ ├── services/ # otp.py, auth.py, security.py, sms.py
│ ├── tasks.py # تسکهای Celery
│ ├── api/v1/ # views / serializers / urls
│ └── tests/ # ۱۴۰ تست
├── docker/ # Dockerfile, entrypoint, nginx
├── locust/ # تست بار
├── screenshots/ # شواهد Postman / Swagger / Admin
├── docker-compose.yml
├── README.md
└── TEST_RESULTS.md
| ابزار | هدف |
|---|---|
pytest + pytest-django |
تست خودکار |
ruff |
لینتر |
black |
قالببندی کد |
bandit |
اسکن امنیتی |
locust |
تست بار |
معین رضایی — 📧 moeinrezaie516@gmail.com







