How Supabase Auth with Google OAuth Works in awesome-gpt-image-2

The awesome-gpt-image-2 repository implements Google OAuth through Supabase by configuring a front-end client with automatic session detection, triggering provider sign-in, and verifying tokens server-side using a service-role key to synchronize user profiles.

The awesome-gpt-image-2 project leverages Supabase's built-in OAuth capabilities to enable seamless Google authentication without custom auth servers. This architecture splits the authentication flow between a browser-based client that handles the OAuth redirect and a back-end API that validates tokens and maintains enriched user profile data. Understanding this implementation reveals how modern applications can securely integrate third-party identity providers while maintaining persistent, refreshable user sessions.

Front-End Supabase Client Configuration

The browser-side authentication begins in src/supabaseClient.js, which initializes a Supabase client when the required environment variables are present. The configuration enables automatic session management through specific auth options that handle the OAuth callback transparently.

import { createClient } from '@supabase/supabase-js';

const supabase = isSupabaseConfigured
  ? createClient(supabaseUrl, supabaseAnonKey, {
      auth: {
        autoRefreshToken: true,
        detectSessionInUrl: true,
        persistSession: true,
      },
    })
  : null;

The detectSessionInUrl: true setting is critical for Google OAuth flow. When Google redirects back to the application after user consent, the access token appears in the URL fragment. Supabase automatically extracts this token, validates it, and creates a session stored in local storage. The persistSession: true option ensures the session survives page reloads, while autoRefreshToken: true handles token renewal automatically before expiration.

Initiating Google Sign-In

To trigger authentication, the UI calls the Supabase client's signIn method with the Google provider identifier. This redirects the user to Google's OAuth 2.0 consent screen without requiring custom redirect handling code.

import { supabase } from '@/supabaseClient';

export async function loginWithGoogle() {
  const { error } = await supabase.auth.signIn({ provider: 'google' });
  if (error) console.error('Google sign-in failed:', error.message);
}

After the user grants permission, Google redirects back to the application with an access_token (and optional refresh_token) appended to the URL fragment. Because the client configuration includes detectSessionInUrl, Supabase automatically processes this callback, creating a session object containing session.user and session.access_token that subsequent API calls use for authentication.

Back-End Token Verification and Profile Synchronization

Protected API routes verify the Supabase token and maintain a local user profile using utilities from api/_lib/supabase.js. Every protected endpoint imports getAuthContext to validate the incoming request and ensure user data stays synchronized with Google account details.

import { getAuthContext } from '../_lib/supabase.js';

export async function handler(req, res) {
  const { user, profile, error } = await getAuthContext(req);
  if (error) {
    res.status(401).json({ error: error });
    return;
  }
  res.json({ user, profile });
}

The getAuthContext function performs several critical operations:

  1. Instantiates an admin client using the SUPABASE_SERVICE_ROLE_KEY to bypass row-level security and perform administrative operations
  2. Extracts the bearer token from the Authorization header (Authorization: Bearer <token>)
  3. Validates the token via client.auth.getUser(token) to confirm the session with Supabase's servers
  4. Synchronizes profile data by calling ensureProfileForUser, which creates or updates a local profiles table row

The profile synchronization logic in api/_lib/supabase.js (lines 78-89) specifically handles Google metadata through the profilePayloadFromUser helper:

function profilePayloadFromUser(user, existingProfile) {
  const metadata = user.user_metadata || {};
  const email = String(user.email || existingProfile?.email || '').trim().toLowerCase();
  const googleName = metadata.full_name || metadata.name || null;
  const googleAvatar = metadata.avatar_url || metadata.picture || null;

  return {
    id: user.id,
    email,
    full_name: existingProfile?.full_name || googleName,
    avatar_url: googleAvatar || existingProfile?.avatar_url || null,
    // other fields …
  };
}

This function pulls Google-specific metadata including the user's name and avatar URL from user.user_metadata, preferring existing profile values while ensuring Google data populates new accounts. The helper also links the profile to user_memberships records when applicable, enabling paid plan verification alongside authentication.

Session Persistence and Protected Routes

The front-end automatically includes the stored bearer token in the Authorization header for subsequent API requests. Because the Supabase client persists the session to localStorage, users remain authenticated across browser sessions without re-authenticating with Google.

On the server side, routes like api/me.js rely on getAuthContext to re-authenticate each request independently. This stateless verification approach ensures that revoked tokens are detected immediately, while the normalizeProfile function assembles the final user object returned to the frontend, combining Supabase auth data, Google metadata, and local membership status.

Summary

  • src/supabaseClient.js initializes the browser client with detectSessionInUrl: true to automatically handle Google OAuth callbacks
  • supabase.auth.signIn({ provider: 'google' }) triggers the OAuth flow without custom redirect logic
  • api/_lib/supabase.js provides getAuthContext for server-side token validation using the service-role key
  • profilePayloadFromUser extracts Google metadata (name, avatar) from user.user_metadata into the local profiles table
  • persistSession: true enables long-lived sessions that survive page reloads while maintaining secure token refresh capabilities

Frequently Asked Questions

How does the front-end detect the Google OAuth callback?

The Supabase client configuration in src/supabaseClient.js sets detectSessionInUrl: true, which instructs the library to automatically scan the URL fragment for access tokens when the page loads. When Google redirects back to the application after authentication, Supabase extracts the token from the URL, validates it, and establishes a session without requiring custom parsing logic in the application code.

What environment variables are required for Supabase Auth with Google OAuth?

The implementation requires VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY for the front-end client initialization, which handle the public OAuth flow. The back-end requires SUPABASE_SERVICE_ROLE_KEY to create an admin client capable of verifying tokens and upserting user profiles in the database. These variables must be present at build time for the client and at runtime for the API routes.

How does the back-end verify the Supabase access token?

Protected routes call getAuthContext from api/_lib/supabase.js, which extracts the bearer token from the Authorization header and passes it to client.auth.getUser(token). This method validates the token against Supabase's authentication servers using the service-role client. Invalid or expired tokens result in an error response, while valid tokens return the user object and trigger profile synchronization.

Where does awesome-gpt-image-2 store Google user metadata?

Google profile data (full name, avatar URL) resides in user.user_metadata returned by Supabase after OAuth authentication. The profilePayloadFromUser function in api/_lib/supabase.js maps these values to the local profiles table, specifically extracting metadata.full_name or metadata.name for the display name and metadata.avatar_url or metadata.picture for the profile image URL.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →