OAuth

Sign in with Google, GitHub, and other OAuth providers.

Note: This is mock/placeholder content for demonstration purposes.

Allow users to sign in with their existing accounts from Google, GitHub, and other providers.

Supported Providers

The auth service supports many OAuth providers:

  • Google
  • GitHub
  • GitLab
  • Bitbucket
  • Azure
  • Facebook
  • Twitter
  • Discord
  • Slack
  • And more...

Setting Up OAuth

Configure OAuth Provider

  1. Configure your desired provider (e.g., Google) in your auth config
  2. Add your OAuth credentials:
    • Client ID
    • Client Secret
    • Redirect URL: https://your-app.com/auth/callback

Google OAuth Setup

  1. Go to Google Cloud Console
  2. Create a new project or select existing
  3. Enable Google+ API
  4. Create OAuth 2.0 credentials
  5. Add authorized redirect URIs:
    • Production: https://your-app.com/auth/callback
    • Development: http://localhost:3000/auth/callback

GitHub OAuth Setup

  1. Go to GitHub Settings → Developer Settings → OAuth Apps
  2. Click "New OAuth App"
  3. Fill in details:
    • Application name: Your App
    • Homepage URL: https://yourapp.com
    • Authorization callback URL: https://your-app.com/auth/callback
  4. Copy Client ID and Client Secret to your auth configuration

Implementation

OAuth Sign In Button

'use client';

import { signInWithOAuthFunction } from '../_lib/functions';

export function OAuthButtons() {
  const handleGoogleSignIn = async () => {
    await signInWithOAuthFunction('google');
  };

  const handleGitHubSignIn = async () => {
    await signInWithOAuthFunction('github');
  };

  return (
    <div className="space-y-2">
      <button
        onClick={handleGoogleSignIn}
        className="w-full flex items-center justify-center gap-2 border rounded-lg p-2"
      >
        <GoogleIcon />
        Continue with Google
      </button>

      <button
        onClick={handleGitHubSignIn}
        className="w-full flex items-center justify-center gap-2 border rounded-lg p-2"
      >
        <GitHubIcon />
        Continue with GitHub
      </button>
    </div>
  );
}

Server Function

import { createServerFn } from '@tanstack/react-start';
import * as z from 'zod';

import { errorMiddleware } from '@kit/function-middleware/server';

const OAuthProviderSchema = z.enum([
  'google',
  'github',
  'gitlab',
  'azure',
  'facebook',
]);

export const signInWithOAuthFunction = createServerFn({ method: 'POST' })
  .middleware([errorMiddleware])
  .validator(OAuthProviderSchema)
  .handler(async ({ data: provider }) => {
    const origin = process.env.VITE_SITE_URL!;

    const url = await signInWithOAuth({
      provider,
      redirectTo: `${origin}/auth/callback`,
    });

    // Redirect to OAuth provider
    throw redirect({ href: url });
  });

OAuth Callback Handler

// src/routes/auth/callback.ts
import { createFileRoute } from '@tanstack/react-router';

import { handleOAuthCallback } from '@kit/auth/server';

export const Route = createFileRoute('/auth/callback')({
  server: {
    handlers: {
      GET: async ({ request }) => {
        const requestUrl = new URL(request.url);
        const code = requestUrl.searchParams.get('code');

        if (code) {
          await handleOAuthCallback(code);
        }

        // Redirect to home page
        return Response.redirect(new URL('/home', request.url));
      },
    },
  },
});

Customizing OAuth Flow

Scopes

Request specific permissions:

await signInWithOAuth({
  provider: 'google',
  scopes: ['email', 'profile', 'https://www.googleapis.com/auth/calendar'],
});

Query Parameters

Pass custom parameters:

await signInWithOAuth({
  provider: 'azure',
  queryParams: {
    prompt: 'consent',
    access_type: 'offline',
  },
});

Skip Browser Redirect

For mobile apps or custom flows:

const url = await signInWithOAuth({
  provider: 'google',
  skipRedirect: true,
});

// url contains the OAuth URL
// Handle redirect manually

Account Linking

Linking Additional Providers

Allow users to link multiple OAuth accounts:

export const linkOAuthProviderFunction = createServerFn({ method: 'POST' })
  .middleware([errorMiddleware, authMiddleware])
  .validator(OAuthProviderSchema)
  .handler(async ({ data: provider }) => {
    const user = await requireAuth();

    const url = await linkOAuthProvider({
      userId: user.id,
      provider,
    });

    throw redirect({ href: url });
  });

Unlinking Providers

export const unlinkOAuthProviderFunction = createServerFn({ method: 'POST' })
  .middleware([errorMiddleware, authMiddleware])
  .validator(
    z.object({
      provider: z.string(),
      identityId: z.string(),
    }),
  )
  .handler(async ({ data }) => {
    await unlinkOAuthProvider({
      identityId: data.identityId,
    });

    // The client re-fetches the linked identities via router.invalidate()
  });

Viewing Linked Identities

import { getSession } from '@kit/auth/server';

export async function getLinkedIdentities() {
  const session = await getSession();

  return session?.user.identities || [];
}

User Data from OAuth

Accessing Provider Data

import { getSession } from '@kit/auth/server';

const session = await getSession();
const user = session?.user;

// User metadata from provider
const {
  name,
  avatarUrl,
  email,
} = user;

// Provider-specific data
const identities = user.identities || [];
const googleIdentity = identities.find(i => i.provider === 'google');

console.log(googleIdentity?.data);

Storing Additional Data

export const completeOAuthProfileFunction = createServerFn({ method: 'POST' })
  .middleware([errorMiddleware, authMiddleware])
  .validator(ProfileSchema)
  .handler(async ({ data }) => {
    const user = await requireAuth();

    // Update user profile
    await updateUserProfile({
      userId: user.id,
      username: data.username,
      bio: data.bio,
      avatarUrl: user.avatarUrl,
    });

    throw redirect({ href: '/home' });
  });

Configuration

Enable OAuth in Config

// config/auth.config.ts
export const authConfig = {
  providers: {
    emailPassword: true,
    oAuth: ['google', 'github'],
  },
};

Conditional Rendering

import { authConfig } from '~/config/auth.config';

export function AuthProviders() {
  return (
    <>
      {authConfig.providers.emailPassword && <EmailPasswordForm />}

      {authConfig.providers.oAuth?.includes('google') && (
        <GoogleSignInButton />
      )}

      {authConfig.providers.oAuth?.includes('github') && (
        <GitHubSignInButton />
      )}
    </>
  );
}

Troubleshooting

Redirect URI Mismatch

Ensure redirect URIs match exactly:

  • Check auth configuration
  • Verify OAuth app settings in provider console
  • Use exact URLs (including http/https)

Missing Email

Some providers don't share email by default:

import { getSession } from '@kit/auth/server';

const session = await getSession();

if (!session?.user.email) {
  // Request email separately or prompt user
  redirect('/auth/complete-profile');
}

Rate Limiting

OAuth providers may rate limit requests:

  • Cache OAuth tokens appropriately
  • Don't make excessive authorization requests
  • Handle rate limit errors gracefully

Best Practices

  1. Request minimum scopes - Only ask for what you need
  2. Handle errors gracefully - OAuth can fail for many reasons
  3. Verify email addresses - Some providers don't verify emails
  4. Support account linking - Let users connect multiple providers
  5. Provide fallback - Always offer email/password as backup
  6. Log OAuth events - Track sign-ins and linking attempts
  7. Test thoroughly - Test with real provider accounts