Social Login Integration Plan¶
1. Executive Summary & Feature Overview¶
1.1 Feature Description¶
- Feature Name: Social Login Integration for Producer Portal
- Feature Type: New Feature
- Priority Level: Medium
1.2 Problem Statement¶
- Current State: The Producer Portal currently only supports email/password authentication via the Django backend. There is placeholder code for GitHub login (
github-auth-button.tsx) but it's not functional. Thesocial-auth-app-django==5.6.0package is already in requirements.txt but not configured. - Pain Points:
- Users must create and remember yet another password
- Friction in the sign-up/sign-in flow reduces adoption
- No SSO capabilities for producer organizations
- User Impact: Producers who prefer social login (Google, GitHub) cannot use their existing accounts
- Business Value: Reduced sign-up friction, improved user experience, potential for higher conversion rates
1.3 Expected Outcomes¶
- Success Metrics:
- Percentage of users choosing social login vs email/password
- Reduced failed login attempts
- Time to complete sign-up flow
- User Experience Goals: One-click sign-in with Google or GitHub
- Technical Goals: Secure OAuth 2.0 implementation with proper account linking
2. Stakeholder Analysis & Requirements¶
2.1 Affected Users & Systems¶
- Primary Users: Producers signing into the Producer Portal
- Secondary Users: Administrators managing user accounts
- System Components:
- Django API (
apps/api/authentication/) - Producer Portal Next.js app (
apps/producer/) - PostgreSQL database (user accounts)
- Integration Points:
- Google OAuth 2.0
- GitHub OAuth 2.0
- Existing JWT authentication system
2.2 Functional Requirements¶
- Must-Have Features:
- Google OAuth login
- GitHub OAuth login
- Account linking (social account to existing email account)
- New user creation from social login
- JWT token generation after social auth
- Should-Have Features:
- Display connected social accounts in user profile
- Ability to disconnect social accounts
- "Remember which provider I used" hint
- Could-Have Features:
- Additional providers (Microsoft, Apple)
- Organization-level SSO (SAML)
- Won't-Have Features (Initial Release):
- Social login for consumer-facing frontend (different user base)
- Social login for check-in app
2.3 Non-Functional Requirements¶
- Performance: OAuth flow should complete within 3 seconds
- Security:
- OAuth 2.0 with PKCE (handled by NextAuth)
- Secure token storage
- CSRF protection
- Email verification from trusted providers
- Accessibility: Social login buttons meet WCAG 2.1 AA standards
- Browser/Platform Support: All modern browsers (Chrome, Firefox, Safari, Edge)
- Reliability: Graceful degradation if OAuth provider is unavailable
3. Current State Analysis¶
3.1 Codebase Research Methodology¶
Primary Tools Used:
- Serena MCP - Code exploration and pattern matching
- mcp__serena__find_file - Located auth files in apps/api/authentication/ and apps/producer/src/lib/auth.ts
- mcp__serena__search_for_pattern - Found all NextAuth usages across producer portal
- mcp__serena__list_dir - Explored directory structures
- mcp__serena__get_symbols_overview - Analyzed auth views structure
- Context7 MCP - Library documentation
- Retrieved NextAuth.js OAuth provider configuration patterns
- Retrieved Django social auth validation patterns
3.2 Existing Architecture & Patterns¶
Tech Stack:
- Backend: Django 5.1.14 with Django REST Framework 3.15.2
- Frontend: Next.js 15.5.2 with React 19, NextAuth 4.24.12
- Authentication: JWT via djangorestframework-simplejwt==5.5.1
- Database: PostgreSQL
Current Authentication Flow:
[Producer Portal] --> POST /auth/token/ --> [Django API]
| |
|<-- JWT access + refresh tokens <----------|
|
v
[NextAuth CredentialsProvider] stores tokens in session
Key Files:
- apps/api/authentication/views.py - Django auth views (login, register, password reset)
- apps/api/authentication/urls.py - Auth URL routing
- apps/api/brktickets/settings.py - Django settings including JWT config
- apps/producer/src/lib/auth.ts - NextAuth configuration with CredentialsProvider
- apps/producer/src/features/auth/components/user-auth-form.tsx - Login form component
- apps/producer/src/features/auth/components/github-auth-button.tsx - Placeholder GitHub button (not functional)
3.3 Relevant Existing Code¶
Django Authentication Views (apps/api/authentication/views.py:39-138):
class EmailTokenObtainPairSerializer(TokenObtainPairSerializer):
username_field = User.EMAIL_FIELD
# Returns: { access, refresh, user: { id, username, email, first_name, last_name } }
class CustomTokenObtainPairView(TokenObtainPairView):
serializer_class = EmailTokenObtainPairSerializer
throttle_classes = [AuthLoginThrottle]
NextAuth Configuration (apps/producer/src/lib/auth.ts:64-194):
export const authOptions: NextAuthOptions = {
providers: [
CredentialsProvider({
// Calls Django /auth/token/ endpoint
// Returns JWT tokens from Django
})
],
callbacks: {
jwt({ token, user }) { /* Stores tokens */ },
session({ session, token }) { /* Exposes tokens to client */ }
},
session: { strategy: 'jwt' }
}
Placeholder GitHub Button (apps/producer/src/features/auth/components/github-auth-button.tsx):
// TODO: Implement Django GitHub OAuth integration
const handleGithubSignIn = async () => {
toast.success('GitHub authentication initiated');
};
3.4 Current Dependencies¶
Already Installed (but not configured):
- social-auth-app-django==5.6.0 - Python Social Auth for Django (not needed for this approach)
- authlib==1.6.5 - OAuth library
Frontend:
- next-auth==4.24.12 - Supports OAuth providers natively with built-in Google and GitHub providers
3.5 Potential Conflicts & Constraints¶
- Account Linking: Need to handle case where social email matches existing account
- Required Fields: Current user model expects
username, social providers may not provide one - Token Format: Social auth must return same JWT format as credentials auth
- Session Strategy: NextAuth uses JWT strategy, must maintain compatibility
4. Research & Best Practices¶
4.1 Industry Standards Research¶
OAuth 2.0 Best Practices:
- Use Authorization Code flow with PKCE (NextAuth handles this automatically)
- Validate state parameter to prevent CSRF (NextAuth handles this)
- Store tokens securely (httpOnly cookies or secure session)
- Validate tokens server-side before trusting user data
Why Frontend-Driven OAuth with NextAuth:
| Aspect | Frontend-Driven (NextAuth) | Backend-Driven (Django) |
|---|---|---|
| OAuth Handling | NextAuth handles all OAuth complexity | Must configure django-allauth |
| Security | PKCE, state validation built-in | Must configure manually |
| Maintenance | NextAuth actively maintained | More Django packages to maintain |
| Complexity | Django only validates tokens | Django handles full OAuth flow |
| UX Control | Frontend controls entire flow | Redirects through backend |
4.2 Framework-Specific Patterns¶
NextAuth Built-in OAuth Providers:
import GoogleProvider from 'next-auth/providers/google';
import GitHubProvider from 'next-auth/providers/github';
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
GitHubProvider({
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
}),
]
5. Solution Design¶
5.1 Proposed Architecture: Frontend-Driven OAuth with NextAuth¶
This approach leverages NextAuth's excellent OAuth handling while keeping Django as the source of truth for user accounts and JWT tokens:
┌─────────────────────────────────────────────────────────────────────┐
│ FRONTEND-DRIVEN OAUTH FLOW (RECOMMENDED) │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ [Producer Portal] │
│ │ │
│ │ 1. User clicks "Sign in with Google/GitHub" │
│ │ │
│ ▼ │
│ [NextAuth] signIn('google') or signIn('github') │
│ │ │
│ │ 2. NextAuth redirects to OAuth provider │
│ │ (handles PKCE, state, nonce automatically) │
│ │ │
│ ▼ │
│ [Google/GitHub OAuth] │
│ │ │
│ │ 3. User authenticates with provider │
│ │ │
│ ▼ │
│ [NextAuth Callback] /api/auth/callback/google │
│ │ │
│ │ 4. NextAuth receives OAuth tokens + user profile │
│ │ - access_token from provider │
│ │ - user email, name, avatar │
│ │ │
│ ▼ │
│ [signIn Callback] (in auth.ts) │
│ │ │
│ │ 5. Call Django API to exchange OAuth token for JWT │
│ │ POST /auth/social/{provider}/ │
│ │ Body: { access_token, email, name, provider } │
│ │ │
│ ▼ │
│ [Django API] │
│ │ │
│ │ 6. Validate OAuth token with provider (security check) │
│ │ 7. Find or create user by email │
│ │ 8. Generate Django JWT tokens │
│ │ 9. Return { access, refresh, user } │
│ │ │
│ ▼ │
│ [NextAuth jwt Callback] │
│ │ │
│ │ 10. Store Django JWT tokens in NextAuth session │
│ │ │
│ ▼ │
│ [Dashboard] - User is logged in with Django JWTs │
│ │
└─────────────────────────────────────────────────────────────────────┘
Why This Approach: 1. NextAuth handles OAuth complexity - PKCE, state validation, token exchange all built-in 2. Django remains source of truth - All users exist in Django, JWTs issued by Django 3. Minimal Django changes - Just add one endpoint to validate social tokens 4. Consistent session handling - Same JWT structure regardless of login method 5. Better UX - Frontend controls the entire flow, no backend redirects
5.2 Data Model Changes¶
No schema migrations required - using existing User model.
Social Account Linking Logic (in Django): 1. Receive OAuth access token + user info from NextAuth 2. Validate the token with the OAuth provider (Google/GitHub API) 3. Extract verified email from validation response 4. Check if user exists with that email: - Yes: Return existing user's JWT tokens - No: Create new user with email as username, return new JWT 5. Optionally store provider info in user profile for audit
5.3 API Design¶
New Django Endpoint:
POST /auth/social/{provider}/
URL Parameters:
- provider: "google" | "github"
Request Body:
{
"access_token": string, // OAuth access token from provider
"email": string, // Email from OAuth profile (for verification)
"name": string, // Full name from OAuth profile
"provider_account_id": string // Provider's user ID
}
Success Response (200):
{
"access": string, // Django JWT access token
"refresh": string, // Django JWT refresh token
"user": {
"id": number,
"username": string,
"email": string,
"first_name": string,
"last_name": string
}
}
Error Responses:
// 400 - Invalid token
{
"error": "invalid_token",
"detail": "OAuth token validation failed"
}
// 400 - Email mismatch
{
"error": "email_mismatch",
"detail": "Token email does not match provided email"
}
// 400 - Unverified email
{
"error": "email_not_verified",
"detail": "Email address not verified by provider"
}
// 429 - Rate limited
{
"error": "rate_limited",
"detail": "Too many authentication attempts"
}
5.4 Security Considerations¶
- Server-Side Token Validation: Django MUST validate the OAuth access token with the provider's API before trusting any user data
- Email Verification: Only accept emails marked as verified by the OAuth provider
- Rate Limiting: Apply existing
AuthLoginThrottleto social auth endpoints - Token Secrecy: Never log OAuth tokens; only log provider and success/failure
- HTTPS Only: All OAuth flows must use HTTPS in production
Token Validation Flow:
Django receives: { access_token, email, name }
│
▼
Call provider API to validate token:
- Google: GET https://oauth2.googleapis.com/tokeninfo?access_token=...
- GitHub: GET https://api.github.com/user (with Bearer token)
│
▼
Verify response email matches provided email
│
▼
Only then create/find user and issue JWT
6. Implementation Plan¶
6.1 Development Phases¶
Phase 1: Django Backend - Social Token Endpoint
- [ ] Create social authentication view to validate OAuth tokens
- [ ] Implement Google token validation (call Google API)
- [ ] Implement GitHub token validation (call GitHub API)
- [ ] Implement user creation/lookup by email
- [ ] Generate JWT tokens for social auth users
- [ ] Add URL route /auth/social/<provider>/
- [ ] Add unit tests for social auth endpoint
- [ ] Deliverable: Working Django API endpoint for social token exchange
Phase 2: NextAuth Configuration
- [ ] Add Google provider to NextAuth config
- [ ] Add GitHub provider to NextAuth config
- [ ] Implement signIn callback to call Django API
- [ ] Update jwt callback to store Django tokens from social auth
- [ ] Handle errors from Django API gracefully
- [ ] Add environment variables for OAuth credentials
- [ ] Deliverable: Working OAuth flow that exchanges tokens with Django
Phase 3: UI Implementation
- [ ] Update github-auth-button.tsx to use signIn('github')
- [ ] Create google-auth-button.tsx component
- [ ] Add social login buttons to sign-in page
- [ ] Add loading states during OAuth redirects
- [ ] Add error handling UI for failed social auth
- [ ] Deliverable: Complete user-facing social login experience
Phase 4: Testing & Polish - [ ] End-to-end testing with real OAuth apps - [ ] Test new user creation via social login - [ ] Test existing user login via social login - [ ] Test error scenarios (invalid token, network failure) - [ ] Security review - [ ] Documentation updates - [ ] Deliverable: Production-ready feature
6.2 Detailed Task Breakdown¶
| Task | Files Affected | Dependencies |
|---|---|---|
| Create social auth view | apps/api/authentication/views.py |
None |
| Add Google token validation | apps/api/authentication/views.py |
View created |
| Add GitHub token validation | apps/api/authentication/views.py |
View created |
| Add social auth URL | apps/api/authentication/urls.py |
View created |
| Add tests for social auth | apps/api/authentication/tests.py |
View created |
| Add Google provider to NextAuth | apps/producer/src/lib/auth.ts |
Django API ready |
| Add GitHub provider to NextAuth | apps/producer/src/lib/auth.ts |
Django API ready |
| Implement signIn callback | apps/producer/src/lib/auth.ts |
Providers added |
| Update GitHub auth button | apps/producer/src/features/auth/components/github-auth-button.tsx |
NextAuth config |
| Create Google auth button | apps/producer/src/features/auth/components/google-auth-button.tsx |
NextAuth config |
| Update sign-in view | apps/producer/src/features/auth/components/sign-in-view.tsx |
Buttons ready |
| Add env variables (dev) | apps/producer/.env.local |
OAuth apps created |
| Add env variables (prod) | Railway environment | OAuth apps created |
6.3 File Change Summary¶
New Files:
- apps/producer/src/features/auth/components/google-auth-button.tsx - Google login button
Modified Files:
- apps/api/authentication/views.py - Add SocialAuthView class
- apps/api/authentication/urls.py - Add /auth/social/<provider>/ route
- apps/producer/src/lib/auth.ts - Add Google/GitHub providers and callbacks
- apps/producer/src/features/auth/components/github-auth-button.tsx - Use signIn('github')
- apps/producer/src/features/auth/components/sign-in-view.tsx - Add social buttons section
- apps/producer/src/features/auth/components/user-auth-form.tsx - Uncomment social buttons section
7. Testing Strategy¶
7.1 Test Coverage Plan¶
Unit Tests (Django): - Test social auth view with mocked OAuth provider responses - Test user creation from social auth data - Test existing user lookup by email - Test token validation error handling - Test rate limiting on social auth endpoint
Integration Tests (Django): - Test full endpoint with mocked Google/GitHub API responses - Test JWT token generation matches credentials auth format
Frontend Tests: - Test NextAuth callback flow with mocked responses - Test error state rendering - Test loading state during OAuth redirect
End-to-End Tests (Manual): - Test with real Google OAuth app - Test with real GitHub OAuth app - Test new user registration - Test existing user login - Test error scenarios
7.2 Test Environment Requirements¶
OAuth Test Apps: - Google OAuth app (Google Cloud Console) with test redirect URIs - GitHub OAuth app with test redirect URIs
Environment Variables (Development):
# Next.js (apps/producer/.env.local)
GOOGLE_CLIENT_ID=xxx
GOOGLE_CLIENT_SECRET=xxx
GITHUB_CLIENT_ID=xxx
GITHUB_CLIENT_SECRET=xxx
Redirect URIs for Development:
- Google: http://localhost:3001/api/auth/callback/google
- GitHub: http://localhost:3001/api/auth/callback/github
Redirect URIs for Production:
- Google: https://portal.piquetickets.com/api/auth/callback/google
- GitHub: https://portal.piquetickets.com/api/auth/callback/github
7.3 Acceptance Criteria¶
- User can sign in with Google OAuth
- User can sign in with GitHub OAuth
- New users are created with correct email/name from OAuth profile
- Existing users (by email) are logged in, not duplicated
- Django JWT tokens are issued after social auth
- Session persists across page reloads
- API calls work with social auth JWT tokens
- Error states are handled gracefully with user-friendly messages
- Loading states shown during OAuth redirects
8. Risk Assessment & Mitigation¶
8.1 Technical Risks¶
| Risk | Probability | Impact | Mitigation Strategy |
|---|---|---|---|
| OAuth provider API changes | Low | Medium | Use official provider APIs, monitor changelogs |
| Token validation failures | Medium | High | Comprehensive error handling, detailed logging |
| Account linking conflicts | Medium | Medium | Auto-link by email, clear error messages |
| Network latency to providers | Low | Low | Timeout handling, retry logic |
8.2 Rollback Strategy¶
- Feature Flag: Social login buttons can be hidden via feature flag
- Database: No schema changes required, rollback is code-only
- Deployment: Can remove OAuth providers from NextAuth config without Django changes
- Users: Users created via social login can still reset password and use credentials
9. Deployment & Operations¶
9.1 Deployment Plan¶
- Create OAuth Apps:
- Google Cloud Console: Create OAuth 2.0 credentials
- GitHub Developer Settings: Create OAuth App
-
Configure redirect URIs for production
-
Configure Environment Variables:
- Add OAuth credentials to Railway environment
-
Ensure
NEXTAUTH_URLis set correctly for production -
Deploy Backend First:
- Deploy Django with social auth endpoint
-
Verify endpoint responds correctly (can test with curl)
-
Deploy Frontend:
- Deploy Producer Portal with OAuth providers
-
Test full flow in staging environment
-
Monitor:
- Watch for authentication errors in Sentry
- Monitor social login success/failure rates
9.2 Monitoring & Observability¶
Key Metrics: - Social login attempts (by provider) - Social login success/failure rate - New user registrations via social login - Token validation response times
Logging (Django):
logger.info(f"Social auth attempt: provider={provider}, email={email}")
logger.info(f"Social auth success: provider={provider}, user_id={user.id}, is_new={is_new}")
logger.warning(f"Social auth failed: provider={provider}, error={error_type}")
# NEVER log access_token or other secrets
10. Success Measurement¶
10.1 Success Metrics¶
- Adoption Rate: % of logins using social auth vs credentials
- Conversion Rate: New sign-ups via social login
- Error Rate: Failed social auth attempts
- User Satisfaction: Reduced password reset requests
11. Appendices¶
Appendix A: OAuth Provider Setup¶
Google OAuth Setup:
1. Go to Google Cloud Console
2. Create new project or select existing
3. Go to "APIs & Services" > "Credentials"
4. Click "Create Credentials" > "OAuth Client ID"
5. Application type: "Web application"
6. Name: "PiqueTickets Producer Portal"
7. Authorized JavaScript origins:
- http://localhost:3001 (development)
- https://portal.piquetickets.com (production)
8. Authorized redirect URIs:
- http://localhost:3001/api/auth/callback/google (development)
- https://portal.piquetickets.com/api/auth/callback/google (production)
9. Copy Client ID and Client Secret
GitHub OAuth Setup:
1. Go to GitHub Settings > Developer Settings > OAuth Apps
2. Click "New OAuth App"
3. Application name: "PiqueTickets Producer Portal"
4. Homepage URL: https://portal.piquetickets.com
5. Authorization callback URL:
- Development: http://localhost:3001/api/auth/callback/github
- Production: https://portal.piquetickets.com/api/auth/callback/github
6. Copy Client ID and generate Client Secret
Appendix B: Code Examples¶
Django Social Auth View (apps/api/authentication/views.py):
import requests
from rest_framework import status
from rest_framework.decorators import api_view, permission_classes, throttle_classes
from rest_framework.response import Response
from rest_framework.permissions import AllowAny
from rest_framework_simplejwt.tokens import RefreshToken
from django.contrib.auth import get_user_model
from utils.throttles import AuthLoginThrottle
User = get_user_model()
@api_view(['POST'])
@permission_classes([AllowAny])
@throttle_classes([AuthLoginThrottle])
def social_auth(request, provider):
"""
Exchange OAuth access token for Django JWT tokens.
Validates the OAuth token with the provider, then creates or finds
the user by email and returns Django JWT tokens.
"""
access_token = request.data.get('access_token')
provided_email = request.data.get('email', '').lower().strip()
name = request.data.get('name', '')
if not access_token or not provided_email:
return Response(
{'error': 'access_token and email are required'},
status=status.HTTP_400_BAD_REQUEST
)
# Validate token with provider
if provider == 'google':
validated_email = validate_google_token(access_token)
elif provider == 'github':
validated_email = validate_github_token(access_token)
else:
return Response(
{'error': 'Invalid provider'},
status=status.HTTP_400_BAD_REQUEST
)
if not validated_email:
return Response(
{'error': 'invalid_token', 'detail': 'OAuth token validation failed'},
status=status.HTTP_400_BAD_REQUEST
)
# Verify email matches
if validated_email.lower() != provided_email:
return Response(
{'error': 'email_mismatch', 'detail': 'Token email does not match'},
status=status.HTTP_400_BAD_REQUEST
)
# Find or create user
user, created = User.objects.get_or_create(
email__iexact=validated_email,
defaults={
'username': validated_email.split('@')[0],
'email': validated_email,
'first_name': name.split()[0] if name else '',
'last_name': ' '.join(name.split()[1:]) if name else '',
}
)
# Generate JWT tokens
refresh = RefreshToken.for_user(user)
return Response({
'access': str(refresh.access_token),
'refresh': str(refresh),
'user': {
'id': user.id,
'username': user.username,
'email': user.email,
'first_name': user.first_name,
'last_name': user.last_name,
}
})
def validate_google_token(access_token):
"""Validate Google OAuth token and return email if valid."""
try:
response = requests.get(
'https://oauth2.googleapis.com/tokeninfo',
params={'access_token': access_token},
timeout=10
)
if response.status_code == 200:
data = response.json()
if data.get('email_verified', 'false') == 'true':
return data.get('email')
except requests.RequestException:
pass
return None
def validate_github_token(access_token):
"""Validate GitHub OAuth token and return email if valid."""
try:
# Get user info
response = requests.get(
'https://api.github.com/user',
headers={'Authorization': f'Bearer {access_token}'},
timeout=10
)
if response.status_code != 200:
return None
user_data = response.json()
# Get emails (primary email might not be in user response)
email_response = requests.get(
'https://api.github.com/user/emails',
headers={'Authorization': f'Bearer {access_token}'},
timeout=10
)
if email_response.status_code == 200:
emails = email_response.json()
for email in emails:
if email.get('primary') and email.get('verified'):
return email.get('email')
# Fallback to user email if available and verified
return user_data.get('email')
except requests.RequestException:
pass
return None
Django URL Configuration (apps/api/authentication/urls.py):
from django.urls import path
from .views import social_auth
urlpatterns = [
# ... existing urls ...
path('social/<str:provider>/', social_auth, name='social_auth'),
]
NextAuth Configuration (apps/producer/src/lib/auth.ts):
import { NextAuthOptions } from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
import GoogleProvider from 'next-auth/providers/google';
import GitHubProvider from 'next-auth/providers/github';
import { JWT } from 'next-auth/jwt';
interface ExtendedToken extends JWT {
accessToken?: string;
refreshToken?: string;
accessTokenExpires?: number;
provider?: string;
error?: string;
}
export const authOptions: NextAuthOptions = {
providers: [
CredentialsProvider({
// ... existing credentials provider ...
}),
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
GitHubProvider({
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
}),
],
callbacks: {
async signIn({ user, account, profile }) {
// Handle social login - exchange OAuth token for Django JWT
if (account?.provider === 'google' || account?.provider === 'github') {
try {
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/auth/social/${account.provider}/`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
access_token: account.access_token,
email: user.email,
name: user.name,
provider_account_id: account.providerAccountId,
}),
}
);
if (!response.ok) {
console.error('Social auth failed:', await response.json());
return false;
}
const djangoTokens = await response.json();
// Store Django tokens on the user object for the jwt callback
(user as any).djangoAccessToken = djangoTokens.access;
(user as any).djangoRefreshToken = djangoTokens.refresh;
(user as any).djangoUser = djangoTokens.user;
return true;
} catch (error) {
console.error('Social auth error:', error);
return false;
}
}
// Credentials login handled by existing logic
return true;
},
async jwt({ token, user, account }) {
// Initial sign in
if (user) {
if (account?.provider === 'google' || account?.provider === 'github') {
// Social login - use Django tokens
return {
...token,
accessToken: (user as any).djangoAccessToken,
refreshToken: (user as any).djangoRefreshToken,
accessTokenExpires: Date.now() + (48 * 60 * 60 * 1000), // 48 hours
provider: account.provider,
};
}
// Credentials login - existing logic
return {
...token,
accessToken: (user as any).accessToken,
refreshToken: (user as any).refreshToken,
accessTokenExpires: (user as any).accessTokenExpires,
};
}
// Return previous token if not expired
const extendedToken = token as ExtendedToken;
if (Date.now() < (extendedToken.accessTokenExpires || 0)) {
return token;
}
// Token expired, refresh it
return refreshAccessToken(extendedToken);
},
async session({ session, token }) {
const extendedToken = token as ExtendedToken;
session.user = {
name: session.user?.name,
email: session.user?.email,
image: session.user?.image,
};
(session.user as any).accessToken = extendedToken.accessToken;
(session.user as any).refreshToken = extendedToken.refreshToken;
(session.user as any).error = extendedToken.error;
return session;
},
},
// ... rest of existing config ...
};
Google Auth Button (apps/producer/src/features/auth/components/google-auth-button.tsx):
'use client';
import { signIn } from 'next-auth/react';
import { Button } from '@/components/ui/button';
import { useState } from 'react';
export default function GoogleSignInButton() {
const [isLoading, setIsLoading] = useState(false);
const handleGoogleSignIn = async () => {
setIsLoading(true);
try {
await signIn('google', { callbackUrl: '/dashboard' });
} catch (error) {
console.error('Google sign in error:', error);
setIsLoading(false);
}
};
return (
<Button
className='w-full'
variant='outline'
type='button'
onClick={handleGoogleSignIn}
disabled={isLoading}
>
{isLoading ? (
<span className="animate-spin mr-2">...</span>
) : (
<svg className="mr-2 h-4 w-4" viewBox="0 0 24 24">
<path
fill="currentColor"
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
/>
<path
fill="currentColor"
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
/>
<path
fill="currentColor"
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
/>
<path
fill="currentColor"
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
/>
</svg>
)}
Continue with Google
</Button>
);
}
Updated GitHub Auth Button (apps/producer/src/features/auth/components/github-auth-button.tsx):
'use client';
import { signIn } from 'next-auth/react';
import { Button } from '@/components/ui/button';
import { Icons } from '@/components/icons';
import { useState } from 'react';
export default function GithubSignInButton() {
const [isLoading, setIsLoading] = useState(false);
const handleGithubSignIn = async () => {
setIsLoading(true);
try {
await signIn('github', { callbackUrl: '/dashboard' });
} catch (error) {
console.error('GitHub sign in error:', error);
setIsLoading(false);
}
};
return (
<Button
className='w-full'
variant='outline'
type='button'
onClick={handleGithubSignIn}
disabled={isLoading}
>
{isLoading ? (
<span className="animate-spin mr-2">...</span>
) : (
<Icons.github className='mr-2 h-4 w-4' />
)}
Continue with GitHub
</Button>
);
}
Document Information: - Created: 2025-11-27 - Last Updated: 2025-11-27 - Version: 2.0 - Author(s): Claude Code - Status: Draft - Ready for Review