OAuth Configuration for Auth Service

This guide explains how to configure OAuth providers (GitHub and Google) for the auth.do.dev unified authentication service.

Required Environment Variables

Add these to your Convex dashboard for the auth deployment (dependable-pika-747):

# Resend (Email OTP)
RESEND_API_KEY=re_xxxxxxxxxxxx
AUTH_RESEND_FROM=auth@notifications.do.dev

# GitHub OAuth
AUTH_GITHUB_ID=xxxxxxxxxxxx
AUTH_GITHUB_SECRET=xxxxxxxxxxxx

# Google OAuth  
AUTH_GOOGLE_ID=xxxxxxxxxxxx.apps.googleusercontent.com
AUTH_GOOGLE_SECRET=xxxxxxxxxxxx

# JWT Secret (generate a random string)
JWT_SECRET=your-random-jwt-secret-string-here

GitHub OAuth Setup

  1. Go to GitHub Settings > Developer settings > OAuth Apps
  2. Click "New OAuth App"
  3. Fill in the application details:
    • Application name: do.dev Auth
    • Homepage URL: https://auth.do.dev (or http://localhost:3030 for dev)
    • Authorization callback URL:
      • Development: http://localhost:3030/api/auth/callback/github
      • Production: https://auth.do.dev/api/auth/callback/github
  4. Click "Register application"
  5. Copy the Client ID and generate a new Client Secret
  6. Add to Convex environment variables:
    • AUTH_GITHUB_ID = Your Client ID
    • AUTH_GITHUB_SECRET = Your Client Secret

Google OAuth Setup

  1. Go to Google Cloud Console
  2. Create a new project or select existing
  3. Enable the Google+ API
  4. Go to "Credentials" and click "Create Credentials" > "OAuth client ID"
  5. Configure the OAuth consent screen first if prompted
  6. For Application type, choose "Web application"
  7. Add authorized JavaScript origins:
    • Development: http://localhost:3030
    • Production: https://auth.do.dev
  8. Add authorized redirect URIs:
    • Development: http://localhost:3030/api/auth/callback/google
    • Production: https://auth.do.dev/api/auth/callback/google
  9. Copy the Client ID and Client Secret
  10. Add to Convex environment variables:
    • AUTH_GOOGLE_ID = Your Client ID
    • AUTH_GOOGLE_SECRET = Your Client Secret

Resend Email Setup

  1. Sign up for Resend
  2. Verify your domain (do.dev) if sending from custom domain
  3. Create an API key
  4. Add to Convex environment variables:

Testing Configuration

Development Testing

  1. Start the auth service:

    cd apps/auth
    pnpm dev --port 3030
  2. Navigate to test URLs:

    • GitHub: http://localhost:3030/login?provider=github&app=dodev&return=http://localhost:3005/dashboard
    • Google: http://localhost:3030/login?provider=google&app=dodev&return=http://localhost:3005/dashboard

Production Testing

  1. Deploy auth service to auth.do.dev
  2. Test OAuth flows:
    • GitHub: https://auth.do.dev/login?provider=github&app=dodev&return=https://do.dev/dashboard
    • Google: https://auth.do.dev/login?provider=google&app=dodev&return=https://do.dev/dashboard

OAuth Flow Implementation

Route Handlers

// apps/auth/app/api/auth/oauth/github/route.ts
import { NextRequest } from "next/server";
import { api } from "@/convex/_generated/api";
import { fetchMutation } from "convex/nextjs";

export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url);
  const app = searchParams.get("app") || "unknown";
  const returnUrl = searchParams.get("return") || "/";
  
  // Initiate OAuth state
  const { state } = await fetchMutation(api.auth.initiateOAuth, {
    provider: "github",
    appId: app,
    returnUrl,
  });
  
  // Redirect to GitHub OAuth
  const githubUrl = new URL("https://github.com/login/oauth/authorize");
  githubUrl.searchParams.set("client_id", process.env.AUTH_GITHUB_ID!);
  githubUrl.searchParams.set("redirect_uri", `${process.env.NEXT_PUBLIC_URL}/api/auth/callback/github`);
  githubUrl.searchParams.set("scope", "user:email");
  githubUrl.searchParams.set("state", state);
  
  return Response.redirect(githubUrl.toString());
}
// apps/auth/app/api/auth/callback/github/route.ts
import { NextRequest } from "next/server";
import { api } from "@/convex/_generated/api";
import { fetchMutation, fetchQuery } from "convex/nextjs";

export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url);
  const code = searchParams.get("code");
  const state = searchParams.get("state");
  
  if (!code || !state) {
    return Response.redirect("/error?message=Invalid OAuth response");
  }
  
  // Validate state and get app info
  const oauthState = await fetchQuery(api.auth.validateOAuthState, { state });
  if (!oauthState) {
    return Response.redirect("/error?message=Invalid OAuth state");
  }
  
  // Exchange code for token
  const tokenResponse = await fetch("https://github.com/login/oauth/access_token", {
    method: "POST",
    headers: {
      "Accept": "application/json",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      client_id: process.env.AUTH_GITHUB_ID,
      client_secret: process.env.AUTH_GITHUB_SECRET,
      code,
    }),
  });
  
  const { access_token } = await tokenResponse.json();
  
  // Get user info from GitHub
  const userResponse = await fetch("https://api.github.com/user", {
    headers: {
      "Authorization": `Bearer ${access_token}`,
    },
  });
  
  const userData = await userResponse.json();
  
  // Create or update user and session
  const session = await fetchMutation(api.auth.completeOAuth, {
    provider: "github",
    providerId: userData.id.toString(),
    email: userData.email,
    name: userData.name || userData.login,
    image: userData.avatar_url,
    appId: oauthState.appId,
    returnUrl: oauthState.returnUrl,
  });
  
  // Redirect with session token
  const redirectUrl = new URL(oauthState.returnUrl);
  redirectUrl.searchParams.set("token", session.token);
  
  return Response.redirect(redirectUrl.toString());
}

Client Integration

// Client-side OAuth trigger
function handleOAuthLogin(provider: "github" | "google") {
  const authUrl = new URL(`${AUTH_SERVICE_URL}/api/auth/oauth/${provider}`);
  authUrl.searchParams.set("app", APP_ID);
  authUrl.searchParams.set("return", window.location.href);
  
  window.location.href = authUrl.toString();
}

Security Considerations

Development

  • Use localhost URLs for development
  • Never commit OAuth secrets to version control
  • Use different OAuth apps for development and production

Production

  • Use HTTPS URLs only
  • Set up proper CORS policies
  • Implement rate limiting
  • Monitor for unusual OAuth activity
  • Regularly rotate OAuth secrets

Troubleshooting

Common Issues

"redirect_uri_mismatch" Error

  • Check that OAuth app redirect URIs exactly match the URLs being used
  • Ensure protocol (http/https) matches
  • Verify port numbers in development

"invalid_client" Error

  • Verify CLIENT_ID and CLIENT_SECRET are correct
  • Ensure OAuth app is enabled
  • Check that secrets are properly set in Convex environment

"access_denied" Error

  • User cancelled OAuth flow
  • Check OAuth app permissions/scopes
  • Verify OAuth consent screen configuration

Debugging

Enable OAuth debugging by adding logs:

// In OAuth handlers
console.log("OAuth initiated:", { provider, app, returnUrl });
console.log("OAuth callback received:", { code: !!code, state });
console.log("User data received:", { email: userData.email, name: userData.name });

View logs in:

  • Convex dashboard for backend logs
  • Browser network tab for client-side issues
  • OAuth provider logs (GitHub/Google Cloud Console)

Migration from Existing Systems

From Clerk

  1. Export user data from Clerk
  2. Map OAuth provider IDs to new system
  3. Update OAuth app redirect URLs
  4. Test OAuth flows thoroughly
  5. Migrate users in batches

From Custom Auth

  1. Audit existing OAuth configurations
  2. Update redirect URLs to auth.do.dev
  3. Migrate user accounts with OAuth provider links
  4. Verify all OAuth flows work correctly

Last updated: January 2025

On this page