Skip to content

setup-oauth

Access: /template setup-oauth or /t setup-oauth

Configures OAuth authentication providers (Google, GitHub, etc.) in your MakerKit project with proper callback handling and user profile management.

What It Does

The setup-oauth command helps you:

  • Configure OAuth providers in Supabase
  • Set up callback handling
  • Implement user profile synchronization
  • Add provider-specific UI components
  • Handle account linking for existing users

Usage

bash
/template setup-oauth "Description"
# or
/t setup-oauth "Description"

When prompted, specify:

  • OAuth provider (Google, GitHub, Facebook, etc.)
  • Scopes required
  • Whether to allow account linking
  • Profile data to sync

Prerequisites

  • A MakerKit project with authentication configured
  • OAuth app credentials from the provider
  • Commercial MakerKit license from MakerKit

What Gets Created

1. Environment Configuration

Updates .env.local with provider credentials:

bash
# Google OAuth
NEXT_PUBLIC_AUTH_PROVIDER_GOOGLE_ENABLED=true
GOOGLE_CLIENT_ID=your-client-id
GOOGLE_CLIENT_SECRET=your-client-secret

# GitHub OAuth  
NEXT_PUBLIC_AUTH_PROVIDER_GITHUB_ENABLED=true
GITHUB_CLIENT_ID=your-client-id
GITHUB_CLIENT_SECRET=your-client-secret

2. Supabase Configuration

SQL migrations for provider setup:

sql
-- Enable OAuth provider
update auth.providers
set enabled = true
where provider = 'google';

-- Configure provider settings
update auth.providers
set settings = jsonb_build_object(
  'client_id', 'your-client-id',
  'client_secret', 'your-client-secret',
  'authorized_redirect_uris', array['https://your-app.com/auth/callback']
)
where provider = 'google';

3. Auth Components

Enhanced auth UI with OAuth buttons:

typescript
// components/auth/oauth-providers.tsx
import { Button } from '@kit/ui/button';
import { createBrowserClient } from '@supabase/ssr';
import { FcGoogle } from 'react-icons/fc';
import { FaGithub } from 'react-icons/fa';

export function OAuthProviders() {
  const supabase = createBrowserClient();
  
  const signInWithProvider = async (provider: 'google' | 'github') => {
    const { error } = await supabase.auth.signInWithOAuth({
      provider,
      options: {
        redirectTo: `${window.location.origin}/auth/callback`,
        scopes: provider === 'github' ? 'read:user user:email' : undefined,
      },
    });
    
    if (error) {
      console.error('OAuth error:', error);
    }
  };
  
  return (
    <div className="space-y-2">
      {process.env.NEXT_PUBLIC_AUTH_PROVIDER_GOOGLE_ENABLED === 'true' && (
        <Button
          variant="outline"
          className="w-full"
          onClick={() => signInWithProvider('google')}
        >
          <FcGoogle className="mr-2 h-4 w-4" />
          Continue with Google
        </Button>
      )}
      
      {process.env.NEXT_PUBLIC_AUTH_PROVIDER_GITHUB_ENABLED === 'true' && (
        <Button
          variant="outline"
          className="w-full"
          onClick={() => signInWithProvider('github')}
        >
          <FaGithub className="mr-2 h-4 w-4" />
          Continue with GitHub
        </Button>
      )}
    </div>
  );
}

4. Callback Handler

Processes OAuth callbacks:

typescript
// app/auth/callback/route.ts
import { NextResponse } from 'next/server';
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs';
import { cookies } from 'next/headers';

export async function GET(request: Request) {
  const requestUrl = new URL(request.url);
  const code = requestUrl.searchParams.get('code');
  const next = requestUrl.searchParams.get('next') ?? '/dashboard';

  if (code) {
    const supabase = createRouteHandlerClient({ cookies });
    
    const { error } = await supabase.auth.exchangeCodeForSession(code);
    
    if (!error) {
      // Sync user profile data
      await syncUserProfile(supabase);
      
      return NextResponse.redirect(new URL(next, requestUrl.origin));
    }
  }

  // Handle error
  return NextResponse.redirect(
    new URL('/auth/error', requestUrl.origin)
  );
}

5. Profile Synchronization

Syncs OAuth provider data:

typescript
// lib/auth/sync-profile.ts
async function syncUserProfile(supabase: SupabaseClient) {
  const { data: { user } } = await supabase.auth.getUser();
  
  if (!user) return;
  
  const profile = {
    id: user.id,
    email: user.email,
    display_name: user.user_metadata.full_name || 
                  user.user_metadata.name ||
                  user.email?.split('@')[0],
    picture_url: user.user_metadata.avatar_url ||
                 user.user_metadata.picture,
    provider: user.app_metadata.provider,
    provider_id: user.user_metadata.provider_id,
  };
  
  // Update user profile
  await supabase
    .from('users')
    .upsert(profile, {
      onConflict: 'id',
    });
}

Provider-Specific Setup

Google OAuth

  1. Create OAuth 2.0 credentials at Google Cloud Console

  2. Add authorized redirect URIs:

    • https://your-project.supabase.co/auth/v1/callback
    • http://localhost:54321/auth/v1/callback (for local dev)
  3. Enable required APIs:

    • Google+ API (for user profile)
    • Gmail API (if email access needed)

GitHub OAuth

  1. Create OAuth App at GitHub Settings

  2. Set Authorization callback URL:

    • https://your-project.supabase.co/auth/v1/callback
  3. Request appropriate scopes:

    • read:user - Read user profile
    • user:email - Access email addresses

Microsoft/Azure AD

  1. Register app in Azure Portal
  2. Configure redirect URIs
  3. Set up required permissions:
    • User.Read - Sign in and read user profile
    • email - View user's email address

Account Linking

Handle existing accounts with same email:

typescript
// Handle account linking in auth callback
const { data: existingUser } = await supabase
  .from('users')
  .select('id')
  .eq('email', user.email)
  .single();

if (existingUser && existingUser.id !== user.id) {
  // Link accounts or handle conflict
  await handleAccountLinking(existingUser.id, user.id);
}

Custom OAuth Flows

typescript
export function useOAuthPopup() {
  const signInWithPopup = async (provider: string) => {
    const width = 500;
    const height = 600;
    const left = window.screenX + (window.outerWidth - width) / 2;
    const top = window.screenY + (window.outerHeight - height) / 2;
    
    const popup = window.open(
      `/auth/oauth/${provider}`,
      'oauth',
      `width=${width},height=${height},left=${left},top=${top}`
    );
    
    // Listen for completion
    window.addEventListener('message', (event) => {
      if (event.data.type === 'oauth-success') {
        popup?.close();
        window.location.href = '/dashboard';
      }
    });
  };
  
  return { signInWithPopup };
}

Mobile App Deep Linking

typescript
// Configure deep linking for mobile OAuth
const { error } = await supabase.auth.signInWithOAuth({
  provider: 'google',
  options: {
    redirectTo: 'myapp://auth/callback',
    skipBrowserRedirect: true,
  },
});

// Handle the returned URL
if (data?.url) {
  // Open in system browser or in-app browser
  await Linking.openURL(data.url);
}

Error Handling

Common OAuth Errors

typescript
// app/auth/error/page.tsx
export default function AuthError({
  searchParams,
}: {
  searchParams: { error?: string; error_description?: string };
}) {
  const error = searchParams.error;
  const description = searchParams.error_description;
  
  return (
    <div className="flex flex-col items-center justify-center min-h-screen">
      <Card className="w-full max-w-md">
        <CardHeader>
          <CardTitle>Authentication Error</CardTitle>
        </CardHeader>
        <CardContent>
          {error === 'access_denied' && (
            <p>You denied access to your account.</p>
          )}
          {error === 'invalid_request' && (
            <p>The authentication request was invalid.</p>
          )}
          {description && (
            <p className="text-sm text-muted-foreground mt-2">
              {description}
            </p>
          )}
          <Button asChild className="w-full mt-4">
            <Link href="/auth/sign-in">Try Again</Link>
          </Button>
        </CardContent>
      </Card>
    </div>
  );
}

Testing OAuth

Local Development

bash
# Use Supabase CLI for local OAuth testing
supabase start

# Configure OAuth providers in local config
supabase functions env set GOOGLE_CLIENT_ID=your-client-id
supabase functions env set GOOGLE_CLIENT_SECRET=your-client-secret

Integration Tests

typescript
describe('OAuth Authentication', () => {
  it('redirects to Google OAuth', async () => {
    const { getByText } = render(<OAuthProviders />);
    const googleButton = getByText('Continue with Google');
    
    fireEvent.click(googleButton);
    
    await waitFor(() => {
      expect(mockSupabase.auth.signInWithOAuth).toHaveBeenCalledWith({
        provider: 'google',
        options: expect.any(Object),
      });
    });
  });
});

Security Considerations

  1. State Parameter: Always use state parameter to prevent CSRF
  2. Nonce Validation: Validate nonce for OpenID Connect providers
  3. Scope Limitations: Only request necessary scopes
  4. Token Storage: Let Supabase handle token storage securely
  5. HTTPS Only: Never use OAuth over HTTP in production

License Requirement

Important: This command requires a commercial MakerKit license from https://makerkit.dev?atp=MqaGgc MakerKit is a premium SaaS starter kit and requires proper licensing for commercial use.

Built with ❤️ for the AI Coding community, by Praney Behl