3. Authentication Flow & User Management

Cloud Challenge CTF supports two authentication paths: Google OAuth for persistent accounts and Guest mode for anonymous exploration. Understanding the authentication flow is essential to understanding how the architecture supports both user types while maintaining security and simplicity.

3.1 Google OAuth Authentication Flow

When a player chooses to authenticate with Google (clicking "Initialize New Agent Protocol" or "Activate Returning Agent Protocol"), the following sequence occurs:

┌─────────────────────────────────────────────────────────────────┐
│                    GOOGLE OAUTH FLOW                            │
│                                                                 │
│  ┌──────────────────────┐                                       │
│  │  Player Clicks Auth  │                                       │
│  │  Button on /login    │                                       │
│  └──────────┬───────────┘                                       │
│             │                                                   │
│             ▼                                                   │
│  ┌──────────────────────────────────────┐                      │
│  │  1. useGoogleLogin Hook Triggers     │                      │
│  │     • Opens Google OAuth consent popup│                      │
│  │     • Client ID from env variable     │                      │
│  │     • Scope: email, profile           │                      │
│  └──────────┬───────────────────────────┘                      │
│             │                                                   │
│             ▼                                                   │
│  ┌──────────────────────────────────────┐                      │
│  │  2. User Authenticates with Google   │                      │
│  │     • User logs into Google account   │                      │
│  │     • Grants permissions to app       │                      │
│  │     • Google returns access_token     │                      │
│  └──────────┬───────────────────────────┘                      │
│             │                                                   │
│             ▼                                                   │
│  ┌──────────────────────────────────────┐                      │
│  │  3. Fetch User Info from Google API  │                      │
│  │     GET https://www.googleapis.com/  │                      │
│  │         oauth2/v2/userinfo            │                      │
│  │     Headers: Authorization: Bearer    │                      │
│  │              {access_token}           │                      │
│  │     Returns: {                        │                      │
│  │       email: "user@gmail.com",       │                      │
│  │       name: "John Doe",              │                      │
│  │       picture: "https://..."         │                      │
│  │     }                                 │                      │
│  └──────────┬───────────────────────────┘                      │
│             │                                                   │
│             ▼                                                   │
│  ┌──────────────────────────────────────┐                      │
│  │  4. Store Session in localStorage     │                      │
│  │     localStorage.setItem('auth_user',│                      │
│  │       JSON.stringify(userInfo))       │                      │
│  │     localStorage.setItem('auth_token',│                      │
│  │       access_token)                   │                      │
│  │     AuthContext.setUser(userInfo)     │                      │
│  └──────────┬───────────────────────────┘                      │
│             │                                                   │
│             ▼                                                   │
│  ┌──────────────────────────────────────┐                      │
│  │  5. Check if User Exists in Database │                      │
│  │     GET /users/check?email=...       │                      │
│  │     → API Gateway                     │                      │
│  │     → Lambda: awschallenge_checkusers│                      │
│  │     → DynamoDB Query (EmailIndex)    │                      │
│  │     Response: { exists: true/false,  │                      │
│  │                user: {...} }          │                      │
│  └──────────┬───────────────────────────┘                      │
│             │                                                   │
│             ├─────────── User Exists? ──────────┐              │
│             │                                    │              │
│             ▼ YES                                ▼ NO           │
│  ┌──────────────────────┐          ┌──────────────────────┐  │
│  │  6a. Returning User  │          │  6b. New User        │  │
│  │      router.push(    │          │      router.push(    │  │
│  │        '/welcomeback')│          │        '/setup')     │  │
│  │      • Show profile   │          │      • Skill setup   │  │
│  │      • Recent progress│          │      • Create profile│  │
│  └──────────────────────┘          └──────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘

Performance: Entire OAuth flow happens in <3 seconds, including Google authentication, user info fetch, and database lookup.

Client-Side OAuth Implementation

The platform implements OAuth entirely client-side using the @react-oauth/google library. This design works seamlessly with Next.js static export, eliminates backend session management complexity, and provides immediate user feedback. OAuth tokens are stored in browser localStorage, which is appropriate for a CTF platform with no financial data or sensitive personal information.

3.2 Guest Mode Authentication Flow

When a player chooses to play as a guest (clicking "Play as Guest"), the following sequence occurs:

┌─────────────────────────────────────────────────────────────────┐
│                    GUEST MODE FLOW                              │
│                                                                 │
│  ┌──────────────────────┐                                       │
│  │  Player Clicks       │                                       │
│  │  "Play as Guest"     │                                       │
│  └──────────┬───────────┘                                       │
│             │                                                   │
│             ▼                                                   │
│  ┌──────────────────────────────────────┐                      │
│  │  1. Generate Guest Credentials       │                      │
│  │     • guestId: 7-char random string  │                      │
│  │       (e.g., "ABC1234")              │                      │
│  │     • guestEmail: guest-{guestId}@   │                      │
│  │       localhost.local                │                      │
│  │     • username: guestId              │                      │
│  └──────────┬───────────────────────────┘                      │
│             │                                                   │
│             ▼                                                   │
│  ┌──────────────────────────────────────┐                      │
│  │  2. Auto-Create Guest Profile        │                      │
│  │     POST /users                      │                      │
│  │     Body: {                          │                      │
│  │       email: "guest-ABC1234@         │                      │
│  │                localhost.local",      │                      │
│  │       username: "ABC1234",          │                      │
│  │       awsSkillLevel: "beginner",     │                      │
│  │       securitySkillLevel: "beginner",│                      │
│  │       puzzleSkillLevel: "beginner"   │                      │
│  │     }                                │                      │
│  │     → API Gateway                     │                      │
│  │     → Lambda: awschallenge_base_user_│                      │
│  │              function                 │                      │
│  │     → DynamoDB Put Item              │                      │
│  │     Response: {                      │                      │
│  │       success: true,                 │                      │
│  │       userId: "uuid-v4"              │                      │
│  │     }                                │                      │
│  └──────────┬───────────────────────────┘                      │
│             │                                                   │
│             ▼                                                   │
│  ┌──────────────────────────────────────┐                      │
│  │  3. Store Session in sessionStorage  │                      │
│  │     sessionStorage.setItem(          │                      │
│  │       'guest_user',                  │                      │
│  │       JSON.stringify({               │                      │
│  │         email: guestEmail,           │                      │
│  │         guestId: guestId,            │                      │
│  │         isGuest: true                │                      │
│  │       })                             │                      │
│  │     )                                │                      │
│  │     AuthContext.setUser({...})       │                      │
│  └──────────┬───────────────────────────┘                      │
│             │                                                   │
│             ▼                                                   │
│  ┌──────────────────────┐                                       │
│  │  4. Redirect to      │                                       │
│  │     /dashboard       │                                       │
│  │     (Skip /setup)    │                                       │
│  │     • Show challenges │                                       │
│  │     • Guest indicator│                                       │
│  └──────────────────────┘                                       │
└─────────────────────────────────────────────────────────────────┘

Guest Mode Design

Guest mode provides anonymous play with auto-generated profiles, lowering the barrier to entry for players who want to try challenges without authentication. This approach works particularly well in educational settings where students may not have Google accounts, and enables rapid testing and development without OAuth configuration. Guest sessions use sessionStorage for temporary persistence, automatically clearing when the browser closes.

Guest Session Management:

  • sessionStorage: Guest sessions stored in sessionStorage (automatically cleared when browser tab closes)
  • Temporary Profiles: Guest profiles remain in DynamoDB but can be cleaned up periodically
  • No Persistence: Guest progress lost when browser closes (encourages account creation for serious players)
  • Auto-Cleanup: Future enhancement: Lambda function to delete guest profiles older than 30 days

3.3 User Profile Structure

All users (authenticated and guest) have the same profile structure in DynamoDB, ensuring consistent API interactions:

interface UserProfile {
  userID: string;              // UUID v4 for all users
  email: string;                // Real email for authenticated, guest-ABC1234@localhost.local for guests
  username: string;             // Display name (real name for authenticated, guest ID for guests)
  awsSkillLevel: string;        // "beginner" | "intermediate" | "advanced"
  securitySkillLevel: string;   // "beginner" | "intermediate" | "advanced"
  puzzleSkillLevel: string;     // "beginner" | "intermediate" | "advanced"
  currentScore: number;         // Total points earned across all challenges
  currentLevel: number;         // Security clearance level (1-3)
  createdAt: string;            // ISO 8601 timestamp
  updatedAt: string;            // ISO 8601 timestamp
  wrongAttempts?: number;       // Challenge attempt tracking (for lockout)
  closeAttempts?: number;       // Close answer attempt tracking
  lockedUntil?: string;         // ISO timestamp for lockout expiration
}

DynamoDB Table: awschallangeUsersTable

  • Primary Key: userID (String, UUID v4)
  • Global Secondary Index: EmailIndex on email field
  • Access Patterns:
    • Lookup by email (for login): Query via EmailIndex
    • Lookup by userID (for profile updates): GetItem on primary key
    • Update user attributes (score, level): UpdateItem with attribute updates

DynamoDB Table: awsChallengeChallengesTable

  • Primary Key: challengeStateId (String, UUID v4)
  • Global Secondary Index: user-challenge-index on userId + challengeId (composite)
  • Access Patterns:
    • Get challenge progress: Query via user-challenge-index
    • Update stage completion: UpdateItem on primary key
interface ChallengeState {
  challengeStateId: string;     // UUID v4
  userId: string;               // Foreign key to userID
  challengeId: string;          // "hallucination", "future_challenges", etc.
  currentStage: number;         // Current stage number (1-4 for Operation Hallucination)
  stageStatus: {                // Per-stage completion tracking
    "1": { completed: 0 | 1, attempts: number },
    "2": { completed: 0 | 1, attempts: number },
    "3": { completed: 0 | 1, attempts: number },
    "4": { completed: 0 | 1, attempts: number }
  };
  lastUpdated: string;          // ISO 8601 timestamp
}

Profile Structure Benefits

The profile structure tracks three skill dimensions (AWS, Security, Puzzle) enabling personalized challenge recommendations. The design is challenge-agnostic, supporting any challenge type without schema changes. Built-in attempt tracking provides lockout mechanisms and anti-cheating measures, while DynamoDB's flexible schema allows adding new attributes without migrations.