Social Login Authentication Bug Fix

Date: November 16, 2025 Status: ✅ Resolved Severity: Critical Affected Systems: OAuth Social Login (Google, Facebook, Apple)

Problem Summary

Users were unable to authenticate using social login providers (Google, Facebook, Apple). After successfully authenticating with the social provider, users were redirected to the callback URL but remained unauthenticated, unable to access protected routes like /dashboard or /admin.

Symptoms

  • User clicks social login button → Successfully redirects to provider
  • User authenticates with Google/Facebook/Apple → Successfully redirects to /auth/callback?code=...&state=...
  • Callback page loads with a code and state parameter
  • User remains unauthenticated - session not created
  • User cannot access dashboard or admin pages
  • Console shows: [Auth] No current user in pool, [Auth] No session found

User Impact

  • 100% failure rate for social login authentication
  • Email/password authentication was working correctly
  • Users could not register or sign in using social providers

Investigation Process

Initial Hypothesis: Session Persistence Issue

We initially suspected the OAuth callback handler wasn’t properly persisting the Cognito session to browser storage.

Investigation Steps:

  1. Added comprehensive debug logging to OAuth flow:
    • /frontend/app/auth/callback/page.tsx - Callback processing logs
    • /frontend/lib/auth/cognito-client.ts - Token exchange and session creation logs
    • /frontend/lib/auth/auth-context.tsx - User loading context logs
  2. Deployed debugging changes to production
  3. Tested social login flow on production (OAuth requires exact redirect URLs)

Finding #1: Cognito SDK Session Storage Issue

The Cognito SDK’s setSignInUserSession() method does not automatically persist the user to localStorage. The getCurrentUser() method relies on a specific localStorage key to retrieve the user:

CognitoIdentityServiceProvider.{clientId}.LastAuthUser

Fix Applied:

// lib/auth/cognito-client.ts:687-691
if (typeof window !== 'undefined') {
  const lastUserKey = `CognitoIdentityServiceProvider.${COGNITO_CONFIG.clientId}.LastAuthUser`;
  localStorage.setItem(lastUserKey, username);
  console.log('[OAuth] Stored LastAuthUser in localStorage:', username);
}

Second Investigation: Lambda Logs

After fixing the session storage issue, we checked the Cognito post-authentication Lambda logs and discovered the actual root cause.

Finding #2: Database Schema Mismatch (Root Cause)

The post-authentication Lambda was failing with a database error:

ERROR: column "last_login_at" of relation "users" does not exist
Position: 31
SQLState: 42703

Critical Discovery:

The OAuth flow was actually working perfectly through all steps:

  1. ✅ User authenticates with social provider
  2. ✅ Cognito receives authorization code
  3. ✅ Cognito exchanges code for tokens
  4. ✅ Cognito triggers post-authentication Lambda
  5. Lambda fails trying to update user in database (missing column)
  6. Cognito rejects the entire authentication because Lambda returned error
  7. ❌ No session created in browser

Root Cause

The database schema was missing the last_login_at column in the users table.

When a user authenticated via social login, Cognito’s post-authentication trigger Lambda attempted to update the user record with the last login timestamp. This SQL update failed because the column didn’t exist, causing Cognito to reject the authentication entirely.

Fixes Applied

1. Cognito Session Storage Fix

File: /frontend/lib/auth/cognito-client.ts Lines: 687-691

Change:

export async function handleOAuthCallback(code: string, state: string): Promise<CognitoUserSession> {
  // ... token exchange code ...

  const session = new CognitoUserSession({
    IdToken,
    AccessToken,
    RefreshToken
  });

  cognitoUser.setSignInUserSession(session);

  // CRITICAL FIX: Manually persist the user to localStorage
  // The Cognito SDK's getCurrentUser() relies on this key to retrieve the user
  if (typeof window !== 'undefined') {
    const lastUserKey = `CognitoIdentityServiceProvider.${COGNITO_CONFIG.clientId}.LastAuthUser`;
    localStorage.setItem(lastUserKey, username);
    console.log('[OAuth] Stored LastAuthUser in localStorage:', username);
  }

  return session;
}

Why This Was Needed:

The amazon-cognito-identity-js SDK’s setSignInUserSession() creates session objects in memory but doesn’t automatically persist the user identifier to localStorage. When the page reloads or when getCurrentUser() is called, the SDK looks for the LastAuthUser key in localStorage to retrieve the cached user. Without this key, the SDK returns null even though valid session tokens exist.

2. Database Schema Fix

Migration Applied:

ALTER TABLE users ADD COLUMN IF NOT EXISTS last_login_at TIMESTAMP WITH TIME ZONE;

Execution:

aws rds-data execute-statement \
  --resource-arn "$DB_CLUSTER_ARN" \
  --secret-arn "$DB_SECRET_ARN" \
  --database momentum \
  --sql "ALTER TABLE users ADD COLUMN IF NOT EXISTS last_login_at TIMESTAMP WITH TIME ZONE;"

Verification:

aws rds-data execute-statement \
  --resource-arn "$DB_CLUSTER_ARN" \
  --secret-arn "$DB_SECRET_ARN" \
  --database momentum \
  --sql "SELECT column_name, data_type FROM information_schema.columns
         WHERE table_name = 'users' AND column_name = 'last_login_at';"

Result:

{
  "records": [
    [
      {"stringValue": "last_login_at"},
      {"stringValue": "timestamp with time zone"}
    ]
  ]
}

Deployment Process

1. Frontend Deployment

./scripts/deployment/deploy-frontend.sh

Steps:

  • ✅ Linting passed
  • ✅ Build successful (Next.js 16.0.3 with Turbopack)
  • ✅ Deployed to S3 bucket: momentum-dev-website
  • ✅ CloudFront distribution updated: E82RZTMX9AHBP
  • ✅ Cache invalidation created

2. Database Migration

source /Users/alpertovi/Development/momentum/.env.infrastructure
aws rds-data execute-statement \
  --resource-arn "$DB_CLUSTER_ARN" \
  --secret-arn "$DB_SECRET_ARN" \
  --database momentum \
  --sql "ALTER TABLE users ADD COLUMN IF NOT EXISTS last_login_at TIMESTAMP WITH TIME ZONE;"

Result: Schema updated successfully

Testing Instructions

Prerequisites

  • Production deployment: https://momentum.cloudnnj.com
  • Browser with clear cache (or use Incognito/Private mode)
  • Access to Google/Facebook/Apple account for testing

Test Steps

  1. Navigate to Sign In Page
    https://momentum.cloudnnj.com/auth/signin
    
  2. Open Browser DevTools
    • Press F12 or Cmd+Option+I (Mac)
    • Go to Console tab
  3. Initiate Social Login
    • Click “Continue with Google” (or Facebook/Apple)
    • Authenticate with your social provider
  4. Verify Successful Authentication
    • You should be redirected to /auth/callback?code=...&state=...
    • Console should show OAuth flow logs:
      [Callback] Processing OAuth callback...
      [OAuth] Starting handleOAuthCallback...
      [OAuth] Token exchange response: {status: 200, ...}
      [OAuth] Stored LastAuthUser in localStorage: [username]
      [Auth] Current user found: [username]
      [Auth] Valid session found
      [AuthContext] User loaded: {user object}
      
    • You should be redirected to /dashboard (or /admin for admin users)
    • You should see your authenticated user interface
  5. Verify Session Persistence
    • Refresh the page (F5)
    • You should remain authenticated
    • Console should show: [Auth] Current user found: [username]
  6. Verify Database Record
    source /Users/alpertovi/Development/momentum/.env.infrastructure
    aws rds-data execute-statement \
      --resource-arn "$DB_CLUSTER_ARN" \
      --secret-arn "$DB_SECRET_ARN" \
      --database momentum \
      --sql "SELECT cognito_sub, email, name, role, email_verified, last_login_at
             FROM users ORDER BY last_login_at DESC LIMIT 5;"
    
    • Verify user record exists
    • Verify last_login_at is populated with recent timestamp

Expected Console Logs

Successful OAuth Flow:

[Callback] Processing OAuth callback...
[Callback] URL params: {hasCode: true, hasState: true, error: null}
[OAuth] Starting handleOAuthCallback...
[OAuth] Config: {hasDomain: true, domain: "momentum-dev-auth", hasClientId: true, region: "us-east-1"}
[OAuth] State verification: {hasExpectedState: true, statesMatch: true}
[OAuth] Token exchange request: {cognitoDomain: "https://...", redirectUri: "https://..."}
[OAuth] Token exchange response: {status: 200, statusText: "OK", ok: true}
[OAuth] Tokens received: {hasIdToken: true, hasAccessToken: true, hasRefreshToken: true}
[OAuth] ID token validated successfully
[OAuth] Creating Cognito user session for: [username]
[OAuth] Token payload: {sub: "...", email: "...", name: "...", role: "..."}
[OAuth] Session created, setting in user pool...
[OAuth] Stored LastAuthUser in localStorage: [username]
[OAuth] Session set successfully
[Callback] Session created successfully: {hasIdToken: true, hasAccessToken: true, isValid: true}
[Callback] Refreshing user context...
[AuthContext] Loading user...
[Auth] Getting current user...
[Auth] Getting current session...
[Auth] Current user found: [username]
[Auth] Valid session found
[Auth] Session found, extracting user info...
[Auth] Token payload: {sub: "...", email: "...", name: "...", role: "..."}
[Auth] User info created: {sub: "...", email: "...", name: "...", role: "..."}
[AuthContext] User loaded: {user object}
[AuthContext] Loading complete
[Callback] Redirecting to dashboard...

Files Modified

Frontend Changes

  1. /frontend/lib/auth/cognito-client.ts
    • Added localStorage persistence for OAuth sessions
    • Lines 687-691: Manual LastAuthUser key storage
  2. /frontend/app/auth/callback/page.tsx
    • Added comprehensive debug logging throughout OAuth callback flow
    • Lines 26-93: Enhanced error handling and logging
  3. /frontend/lib/auth/auth-context.tsx
    • Added debug logging to user loading process
    • Lines 61-73: Enhanced loadUser function logging

Backend Changes

  1. Database Schema
    • Added last_login_at TIMESTAMP WITH TIME ZONE column to users table
    • Migration applied via AWS RDS Data API

Lessons Learned

1. Check Lambda Logs Early

The actual root cause was in the backend Lambda, not the frontend. We spent time debugging the frontend first before checking the Lambda logs. In future OAuth issues, check Lambda logs immediately.

2. Cognito SDK Session Management

The amazon-cognito-identity-js SDK has specific requirements for session persistence:

  • setSignInUserSession() is not sufficient for full persistence
  • Must manually set LastAuthUser key in localStorage
  • This behavior is not well-documented in AWS Cognito docs

3. OAuth Testing Requires Production

OAuth callbacks require exact URL matching, making local testing impossible. All OAuth debugging must be done on production or staging with proper callback URLs configured.

4. Cascading Failures

A backend database error can manifest as a frontend authentication issue. The frontend code was actually correct, but the authentication failed upstream due to the Lambda error. This highlights the importance of checking the entire authentication pipeline.

5. CloudFront Caching

When deploying frontend fixes, always create a CloudFront cache invalidation:

aws cloudfront create-invalidation --distribution-id E82RZTMX9AHBP --paths "/*"

Additional Notes

Database Schema Update Needed in Documentation

The CLAUDE.md file shows the users table schema but was missing the last_login_at column. This should be updated to reflect the current schema:

CREATE TABLE users (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  cognito_sub VARCHAR(255) UNIQUE NOT NULL,
  email VARCHAR(255) UNIQUE NOT NULL,
  name VARCHAR(255) NOT NULL,
  role VARCHAR(50) NOT NULL DEFAULT 'FREE',
  avatar_url TEXT,
  email_verified BOOLEAN DEFAULT FALSE,
  last_login_at TIMESTAMP WITH TIME ZONE,  -- Added in this fix
  created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

Future Improvements

  1. Add Database Migration Scripts: Create a proper migration framework instead of running ad-hoc SQL
  2. Integration Tests for OAuth: Set up automated testing for social login flows
  3. Better Error Messages: Surface Lambda errors to the frontend for better debugging
  4. Schema Validation: Add schema validation checks before Lambda deployments

Conclusion

The social login bug was caused by a missing database column (last_login_at) that caused the Cognito post-authentication Lambda to fail. This resulted in Cognito rejecting the entire authentication flow, even though the OAuth callback and token exchange were working correctly.

The fix involved:

  1. Adding the missing database column
  2. Fixing Cognito SDK session persistence (localStorage key)
  3. Adding comprehensive logging for future debugging

Status: ✅ RESOLVED - Social login is now fully functional in production.


Back to top

Momentum LMS © 2025. Distributed under the MIT license.