Skip to content

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. The social-auth-app-django==5.6.0 package 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

  1. Server-Side Token Validation: Django MUST validate the OAuth access token with the provider's API before trusting any user data
  2. Email Verification: Only accept emails marked as verified by the OAuth provider
  3. Rate Limiting: Apply existing AuthLoginThrottle to social auth endpoints
  4. Token Secrecy: Never log OAuth tokens; only log provider and success/failure
  5. 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

  1. Create OAuth Apps:
  2. Google Cloud Console: Create OAuth 2.0 credentials
  3. GitHub Developer Settings: Create OAuth App
  4. Configure redirect URIs for production

  5. Configure Environment Variables:

  6. Add OAuth credentials to Railway environment
  7. Ensure NEXTAUTH_URL is set correctly for production

  8. Deploy Backend First:

  9. Deploy Django with social auth endpoint
  10. Verify endpoint responds correctly (can test with curl)

  11. Deploy Frontend:

  12. Deploy Producer Portal with OAuth providers
  13. Test full flow in staging environment

  14. Monitor:

  15. Watch for authentication errors in Sentry
  16. 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