# ADR-005: Authentication Approach ## Status Accepted ## Context ThrillWiki needs to authenticate users for: - Web browsing (session-based) - API access (token-based) - Social login (Google, Discord) We needed an authentication approach that would: - Support multiple authentication methods - Provide secure token handling for API - Enable social authentication - Work seamlessly with Django + HTMX architecture ## Decision We implemented a **Hybrid Authentication System** using django-allauth for social auth and djangorestframework-simplejwt for API tokens. ### Authentication Methods | Context | Method | Library | |---------|--------|---------| | Web browsing | Session-based | Django sessions | | API access | JWT tokens | djangorestframework-simplejwt | | Social login | OAuth2 | django-allauth | | Password reset | Email tokens | Django built-in | ### Architecture ``` ┌─────────────────────────────────────────────────────────┐ │ User Request │ └─────────────────────────────────────────────────────────┘ │ ┌───────────────┼───────────────┐ │ │ │ ▼ ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ Web Request │ │ API Request │ │ Social Auth │ │ │ │ │ │ │ │ Session Cookie │ │ Bearer Token │ │ OAuth Flow │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │ │ └───────────────┼───────────────┘ ▼ ┌─────────────────────┐ │ Django User │ │ (Authenticated) │ └─────────────────────┘ ``` ### JWT Token Configuration ```python # backend/config/settings/rest_framework.py SIMPLE_JWT = { 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=15), 'REFRESH_TOKEN_LIFETIME': timedelta(days=7), 'ROTATE_REFRESH_TOKENS': True, 'BLACKLIST_AFTER_ROTATION': True, 'ALGORITHM': 'HS256', 'SIGNING_KEY': SECRET_KEY, 'AUTH_HEADER_TYPES': ('Bearer',), } ``` ### Social Authentication ```python # backend/config/settings/third_party.py SOCIALACCOUNT_PROVIDERS = { 'google': { 'SCOPE': ['profile', 'email'], 'AUTH_PARAMS': {'access_type': 'online'}, }, 'discord': { 'SCOPE': ['identify', 'email'], }, } ``` ## Consequences ### Benefits 1. **Flexibility**: Multiple auth methods for different use cases 2. **Security**: JWT with short-lived access tokens 3. **User Experience**: Social login reduces friction 4. **Standards-Based**: OAuth2 and JWT are industry standards 5. **Django Integration**: Seamless with Django's user model ### Trade-offs 1. **Complexity**: Multiple auth systems to maintain 2. **Token Management**: Must handle token refresh client-side 3. **Social Provider Dependency**: Reliance on third-party OAuth providers ### Authentication Flow #### Web Session Authentication ``` 1. User visits /login/ 2. User submits credentials 3. Django validates credentials 4. Session created, cookie set 5. Subsequent requests include session cookie ``` #### API JWT Authentication ``` 1. Client POST /api/v1/auth/login/ {username, password} 2. Server validates, returns tokens {access: "...", refresh: "..."} 3. Client includes in requests: Authorization: Bearer 4. On 401, client refreshes: POST /api/v1/auth/token/refresh/ {refresh: "..."} ``` #### Social Authentication ``` 1. User clicks "Login with Google" 2. Redirect to Google OAuth 3. User authorizes application 4. Google redirects with auth code 5. Server exchanges code for tokens 6. Server creates/updates user 7. Session created ``` ## Alternatives Considered ### Session-Only Authentication **Rejected because:** - Not suitable for mobile apps - Not RESTful for API access - CSRF complexity for API clients ### JWT-Only Authentication **Rejected because:** - More complex for web browsing - Token storage in browser has security concerns - Session logout not immediate ### OAuth2 Server (Self-Hosted) **Rejected because:** - Significant complexity for current needs - django-oauth-toolkit overkill - django-allauth sufficient for social auth ## Implementation Details ### Permission Classes ```python # API views use JWT or session authentication REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework_simplejwt.authentication.JWTAuthentication', 'rest_framework.authentication.SessionAuthentication', ], 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.IsAuthenticatedOrReadOnly', ], } ``` ### Custom User Model ```python # backend/apps/accounts/models.py class User(AbstractUser): email = models.EmailField(unique=True) display_name = models.CharField(max_length=50, blank=True) avatar_url = models.URLField(blank=True) email_verified = models.BooleanField(default=False) USERNAME_FIELD = 'email' REQUIRED_FIELDS = ['username'] ``` ### Email Verification ```python # Required before full access ACCOUNT_EMAIL_VERIFICATION = 'mandatory' ACCOUNT_EMAIL_REQUIRED = True ACCOUNT_AUTHENTICATION_METHOD = 'email' ``` ### Security Measures 1. **Password Hashing**: Django's PBKDF2 with SHA256 2. **Token Blacklisting**: Invalidated refresh tokens stored 3. **Rate Limiting**: Login attempts limited 4. **HTTPS Required**: Tokens only sent over secure connections ## References - [django-allauth Documentation](https://django-allauth.readthedocs.io/) - [djangorestframework-simplejwt](https://django-rest-framework-simplejwt.readthedocs.io/) - [OAuth 2.0 Specification](https://oauth.net/2/) - [JWT Best Practices](https://auth0.com/blog/jwt-security-best-practices/)