KitRocket

Add an OAuth Provider

Add Twitter/X, Discord, or any other OAuth provider to your KitRocket auth system.

KitRocket uses Better Auth, which supports 20+ OAuth providers out of the box. This guide walks through adding a new one using Twitter/X as an example.

Step 1: Register your app with the provider

Go to the Twitter Developer Portal and create a new app.

Set the callback URL to:

http://localhost:3000/api/auth/callback/twitter

For production, also add:

https://yourdomain.com/api/auth/callback/twitter

Note the Client ID and Client Secret.

Step 2: Add environment variables

TWITTER_CLIENT_ID="your-twitter-client-id"
TWITTER_CLIENT_SECRET="your-twitter-client-secret"

Step 3: Configure Better Auth

Add the provider to your auth server configuration:

import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { db } from "@/lib/db";

export const auth = betterAuth({
  database: drizzleAdapter(db),
  secret: process.env.AUTH_SECRET,
  baseURL: process.env.BETTER_AUTH_URL,
  emailAndPassword: {
    enabled: true,
  },
  socialProviders: {
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    },
    github: {
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    },
    // Add the new provider:
    twitter: {
      clientId: process.env.TWITTER_CLIENT_ID!,
      clientSecret: process.env.TWITTER_CLIENT_SECRET!,
    },
  },
});

Step 4: Add a login button

Add a Twitter sign-in button to the OAuth buttons component:

"use client";

import { signIn } from "@/lib/auth/client";
import { Button } from "@/components/ui/button";

export function OAuthButtons() {
  return (
    <div className="flex flex-col gap-2">
      <Button
        variant="outline"
        className="w-full"
        onClick={() => signIn.social({ provider: "google" })}
      >
        Continue with Google
      </Button>
      <Button
        variant="outline"
        className="w-full"
        onClick={() => signIn.social({ provider: "github" })}
      >
        Continue with GitHub
      </Button>
      {/* Add the new button: */}
      <Button
        variant="outline"
        className="w-full"
        onClick={() => signIn.social({ provider: "twitter" })}
      >
        Continue with Twitter
      </Button>
    </div>
  );
}

Step 5: Test

  1. Run pnpm dev
  2. Go to http://localhost:3000/login
  3. Click "Continue with Twitter"
  4. You should be redirected to Twitter's OAuth consent screen
  5. After authorizing, you're redirected back and logged in
  6. Check the accounts table — a new row with provider: "twitter" should exist

Discord

discord: {
  clientId: process.env.DISCORD_CLIENT_ID!,
  clientSecret: process.env.DISCORD_CLIENT_SECRET!,
},

Register at Discord Developer Portal. Callback URL: /api/auth/callback/discord.

Apple

apple: {
  clientId: process.env.APPLE_CLIENT_ID!,
  clientSecret: process.env.APPLE_CLIENT_SECRET!,
},

Register at Apple Developer. Note: Apple sign-in requires additional configuration (team ID, key file).

LinkedIn

linkedin: {
  clientId: process.env.LINKEDIN_CLIENT_ID!,
  clientSecret: process.env.LINKEDIN_CLIENT_SECRET!,
},

Register at LinkedIn Developer. Callback URL: /api/auth/callback/linkedin.

Production checklist

After testing locally:

  1. Add the production callback URL to the OAuth provider's settings
  2. Add the environment variables to Vercel (or your hosting platform)
  3. Redeploy
  4. Test the flow on the production URL
  5. Verify accounts are created correctly in the database

Troubleshooting

"OAuth callback URL mismatch"

The callback URL registered with the provider must exactly match what Better Auth generates. The format is:

{BETTER_AUTH_URL}/api/auth/callback/{provider}

Check that BETTER_AUTH_URL is set correctly for your environment.

"Invalid client_id"

Double-check the environment variables. Make sure there are no trailing spaces or newlines in the values.

User logged in but no name/avatar

Some providers don't return profile data by default. Check the provider's OAuth scope settings and request the appropriate scopes (e.g., profile, email).

On this page