Authentication Project — Complete Multi-Strategy Auth System
In this tutorial, you will learn about Authentication Project. We cover key concepts, practical examples, and best practices to help you master this topic.
Build a production-ready authentication gateway that supports multiple authentication strategies including JWT, session-based auth, API keys, and OAuth 2.0, with MFA support, role-based access control, and comprehensive security logging.
What You'll Learn
By the end of this project, you will have built a complete authentication system that handles user registration, login with optional MFA, token management with refresh rotation, role-based authorization, and API key management for service accounts.
Why It Matters
This project integrates all the authentication patterns you have learned into a single cohesive system. It is directly applicable to real-world applications and can serve as a foundation for production authentication infrastructure.
Real-World Use
DodaTech's services use a similar multi-Strategy auth gateway. Web users authenticate via session cookies, mobile apps via JWT with refresh tokens, third-party integrations via OAuth 2.0, and internal Microservices via API keys with mutual TLS.
Auth Gateway Architecture
flowchart TB
subgraph "Clients"
W[Web Browser]
M[Mobile App]
S[Third-Party]
I[Internal Service]
end
subgraph "Auth Gateway"
JWT[JWT Auth]
SESS[Session Auth]
OAUTH[OAuth 2.0]
KEY[API Key Auth]
end
subgraph "Core"
USR[User Service]
MFA[MFA Service]
TOK[Token Manager]
AUD[Audit Logger]
end
subgraph "Storage"
DB[(Database)]
RD[(Redis)]
end
W --> SESS
M --> JWT
S --> OAUTH
I --> KEY
SESS --> USR
JWT --> USR
OAUTH --> USR
KEY --> USR
USR --> MFA
USR --> TOK
USR --> AUD
USR --> DB
TOK --> RD
Multi-Strategy Auth Gateway
import jwt
import time
import hashlib
import secrets
import json
from datetime import datetime, timedelta
class User:
def __init__(self, user_id, email, password_hash, role="user", mfa_secret=None):
self.id = user_id
self.email = email
self.password_hash = password_hash
self.role = role
self.mfa_secret = mfa_secret
self.api_keys = []
self.sessions = []
class AuthGateway:
def __init__(self):
self.secret_key = secrets.token_hex(32)
self.users = {}
self.next_id = 1
def register_user(self, email, password, role="user"):
password_hash = hashlib.sha256(password.encode()).hexdigest()
user = User(self.next_id, email, password_hash, role)
self.users[self.next_id] = user
self.next_id += 1
print(f"[Gateway] Registered: {email} (ID: {user.id})")
return user
def authenticate_session(self, email, password):
user = self._find_by_email(email)
if not user or user.password_hash != hashlib.sha256(password.encode()).hexdigest():
return None
session = {
"session_id": secrets.token_urlsafe(32),
"user_id": user.id,
"created_at": datetime.utcnow(),
"expires_at": datetime.utcnow() + timedelta(days=1),
}
user.sessions.append(session)
print(f"[Gateway] Session auth: {email}")
return {"session": session["session_id"], "user_id": user.id, "role": user.role}
def authenticate_jwt(self, email, password, mfa_code=None):
user = self._find_by_email(email)
if not user or user.password_hash != hashlib.sha256(password.encode()).hexdigest():
return None
if user.mfa_secret and not mfa_code:
return {"require_mfa": True, "message": "MFA code required"}
access = jwt.encode({
"sub": user.id, "email": user.email, "role": user.role,
"type": "access", "exp": int(time.time()) + 900,
}, self.secret_key, algorithm="HS256")
refresh = jwt.encode({
"sub": user.id, "type": "refresh",
"exp": int(time.time()) + 604800,
}, self.secret_key, algorithm="HS256")
print(f"[Gateway] JWT auth: {email}")
return {"access_token": access, "refresh_token": refresh, "expires_in": 900}
def authenticate_api_key(self, api_key):
for user in self.users.values():
for key in user.api_keys:
if key["key"] == api_key and not key["revoked"]:
if datetime.utcnow() < key["expires_at"]:
print(f"[Gateway] API key auth: {user.email}")
return {"user_id": user.id, "role": user.role, "client": key["name"]}
return None
def generate_api_key(self, user_id, name, tier="standard"):
user = self.users.get(user_id)
if not user:
return None
key = f"sk_{secrets.token_urlsafe(32)}"
user.api_keys.append({
"key": key, "name": name, "tier": tier,
"created_at": datetime.utcnow(),
"expires_at": datetime.utcnow() + timedelta(days=365),
"revoked": False,
})
print(f"[Gateway] API key generated for {user.email}")
return key
def enable_mfa(self, user_id):
user = self.users.get(user_id)
if not user:
return None
import pyotp
user.mfa_secret = pyotp.random_base32()
print(f"[Gateway] MFA enabled for {user.email}")
return user.mfa_secret
def _find_by_email(self, email):
for user in self.users.values():
if user.email == email:
return user
return None
gateway = AuthGateway()
gateway.register_user("alice@example.com", "secure-password", "admin")
gateway.register_user("bob@example.com", "password123", "user")
print("Session auth:", gateway.authenticate_session("alice@example.com", "secure-password"))
print("JWT auth:", gateway.authenticate_jwt("alice@example.com", "secure-password"))
key = gateway.generate_api_key(1, "Production App", "premium")
print("API key:", key)
print("API key auth:", gateway.authenticate_api_key(key))
Expected output:
[Gateway] Registered: alice@example.com (ID: 1)
[Gateway] Registered: bob@example.com (ID: 2)
[Gateway] Session auth: alice@example.com
Session auth: {'session': 'abc...', 'user_id': 1, 'role': 'admin'}
[Gateway] JWT auth: alice@example.com
JWT auth: {'access_token': 'eyJ...', 'refresh_token': 'eyJ...', 'expires_in': 900}
[Gateway] API key generated for alice@example.com
[Gateway] API key auth: alice@example.com
Practice Questions
- How does the auth gateway handle multiple authentication strategies?
It checks each strategy in order: try JWT first, fall back to API key, then session, then OAuth 2.0. The first successful authentication short-circuits. Each strategy has its own verification logic.
- Why separate the MFA check from the initial authentication?
The authentication gateway first verifies the password, then checks if MFA is required. This two-step flow allows the client to prompt for MFA code separately from the password entry, improving UX.
- How does the project handle token revocation?
Session tokens are checked against the user's active session list. JWT refresh tokens are tracked in Redis. API keys have a revoked flag. Each strategy has its own revocation mechanism appropriate to its nature.
FAQ
Mini Project: Auth Test Suite
Build a comprehensive test suite that validates all authentication strategies, token management, MFA flows, and error handling.
import unittest
import time
class TestAuthGateway(unittest.TestCase):
def setUp(self):
self.gateway = AuthGateway()
self.gateway.register_user("test@test.com", "password123", "admin")
def test_session_auth_success(self):
result = self.gateway.authenticate_session("test@test.com", "password123")
self.assertIsNotNone(result)
self.assertEqual(result["role"], "admin")
def test_session_auth_failure(self):
result = self.gateway.authenticate_session("test@test.com", "wrong-password")
self.assertIsNone(result)
def test_jwt_auth(self):
result = self.gateway.authenticate_jwt("test@test.com", "password123")
self.assertIn("access_token", result)
def test_api_key_auth(self):
key = self.gateway.generate_api_key(1, "Test App")
result = self.gateway.authenticate_api_key(key)
self.assertIsNotNone(result)
self.assertEqual(result["role"], "admin")
def test_api_key_revocation(self):
key = self.gateway.generate_api_key(1, "Test App")
user = self.gateway.users[1]
user.api_keys[0]["revoked"] = True
result = self.gateway.authenticate_api_key(key)
self.assertIsNone(result)
def test_mfa_required_flag(self):
self.gateway.enable_mfa(1)
result = self.gateway.authenticate_jwt("test@test.com", "password123")
self.assertEqual(result.get("require_mfa"), True)
if __name__ == "__main__":
unittest.main()
What's Next
Explore backend security to learn how to protect authentication endpoints, then dive into caching strategies for optimizing session store performance.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro