Authentication
Social OAuth
Add social login with Google, GitHub, Apple, Microsoft, and 6 more providers — complete setup guides for each.
Banata Auth supports 10 social OAuth providers out of the box. Users can sign in with their existing accounts from these services, reducing friction and increasing sign-up conversion.
Supported Providers
| Provider | Config Key | OAuth Type | Notes |
|---|---|---|---|
google | OAuth 2.0 / OIDC | Most common. Supports Google Workspace. | |
| GitHub | github | OAuth 2.0 | Popular for developer tools. |
| Apple | apple | OAuth 2.0 / OIDC | Required for iOS App Store apps. Requires yearly key rotation. |
| Microsoft | microsoft | OAuth 2.0 / OIDC | Supports Azure AD. Requires tenantId. |
facebook | OAuth 2.0 | Large consumer user base. | |
twitter | OAuth 2.0 | Also known as X. | |
| Discord | discord | OAuth 2.0 | Popular for gaming and communities. |
| Spotify | spotify | OAuth 2.0 | For music-related apps. |
| Twitch | twitch | OAuth 2.0 / OIDC | For streaming and gaming apps. |
linkedin | OAuth 2.0 / OIDC | For professional/B2B apps. |
Configuration
Add social providers to your BanataAuthConfig:
// convex/banataAuth/auth.ts
function buildConfig(): BanataAuthConfig {
return {
appName: "My App",
siteUrl: process.env.SITE_URL!,
secret: process.env.BETTER_AUTH_SECRET!,
authMethods: {
emailPassword: true, // Keep email/password alongside social
},
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 more providers as needed...
},
};
}Setting Provider Credentials
OAuth credentials must be set on the Convex deployment (not in .env.local), since they're used by server-side Convex functions:
# Google
npx convex env set GOOGLE_CLIENT_ID "your-google-client-id"
npx convex env set GOOGLE_CLIENT_SECRET "your-google-client-secret"
# GitHub
npx convex env set GITHUB_CLIENT_ID "your-github-client-id"
npx convex env set GITHUB_CLIENT_SECRET "your-github-client-secret"
# Apple
npx convex env set APPLE_CLIENT_ID "your-apple-service-id"
npx convex env set APPLE_CLIENT_SECRET "your-apple-client-secret"
# Microsoft
npx convex env set MICROSOFT_CLIENT_ID "your-microsoft-client-id"
npx convex env set MICROSOFT_CLIENT_SECRET "your-microsoft-client-secret"
npx convex env set MICROSOFT_TENANT_ID "your-tenant-id"Provider Setup Guides
- Go to the Google Cloud Console
- Create a new project (or select existing)
- Navigate to APIs & Services > Credentials
- Click Create Credentials > OAuth client ID
- Select Web application
- Add authorized redirect URI:
http://localhost:3000/api/auth/callback/google - Copy the Client ID and Client Secret
npx convex env set GOOGLE_CLIENT_ID "123456789.apps.googleusercontent.com"
npx convex env set GOOGLE_CLIENT_SECRET "GOCSPX-..."socialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
},
},Production: Update the redirect URI to
https://your-domain.com/api/auth/callback/google.
GitHub
- Go to GitHub Developer Settings
- Click New OAuth App
- Set Homepage URL:
http://localhost:3000 - Set Authorization callback URL:
http://localhost:3000/api/auth/callback/github - Copy the Client ID
- Generate a new Client Secret
npx convex env set GITHUB_CLIENT_ID "Ov23li..."
npx convex env set GITHUB_CLIENT_SECRET "your-secret"socialProviders: {
github: {
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
},
},Apple
Apple Sign-In requires more setup than other providers:
- Go to Apple Developer Portal
- Navigate to Certificates, Identifiers & Profiles > Identifiers
- Create a new App ID with Sign in with Apple enabled
- Create a Services ID (this is your
clientId) - Configure the Web Authentication domain and redirect URL:
http://localhost:3000/api/auth/callback/apple - Create a Key with Sign in with Apple enabled
- Generate the client secret using the key (Apple uses JWT-based secrets)
npx convex env set APPLE_CLIENT_ID "com.myapp.auth"
npx convex env set APPLE_CLIENT_SECRET "your-generated-jwt-secret"socialProviders: {
apple: {
clientId: process.env.APPLE_CLIENT_ID!,
clientSecret: process.env.APPLE_CLIENT_SECRET!,
},
},Important: Apple client secrets expire after 6 months. You'll need to regenerate them periodically.
Microsoft (Azure AD)
- Go to the Azure Portal
- Click New registration
- Set redirect URI:
http://localhost:3000/api/auth/callback/microsoft(Web platform) - Note the Application (client) ID and Directory (tenant) ID
- Go to Certificates & secrets > New client secret
- Copy the secret value
npx convex env set MICROSOFT_CLIENT_ID "your-app-id"
npx convex env set MICROSOFT_CLIENT_SECRET "your-secret-value"
npx convex env set MICROSOFT_TENANT_ID "your-tenant-id"socialProviders: {
microsoft: {
clientId: process.env.MICROSOFT_CLIENT_ID!,
clientSecret: process.env.MICROSOFT_CLIENT_SECRET!,
tenantId: process.env.MICROSOFT_TENANT_ID!, // Required for Microsoft
},
},Tenant ID options:
- Specific tenant — Only users from that Azure AD tenant
"common"— Any Microsoft account (personal + work/school)"organizations"— Work/school accounts only"consumers"— Personal Microsoft accounts only
- Go to Meta for Developers
- Create a new app (Consumer type)
- Add Facebook Login product
- Set Valid OAuth Redirect URIs:
http://localhost:3000/api/auth/callback/facebook - Go to Settings > Basic for App ID and App Secret
socialProviders: {
facebook: {
clientId: process.env.FACEBOOK_CLIENT_ID!,
clientSecret: process.env.FACEBOOK_CLIENT_SECRET!,
},
},Twitter (X)
- Go to the Twitter Developer Portal
- Create a new project and app
- Enable OAuth 2.0
- Set Callback URL:
http://localhost:3000/api/auth/callback/twitter - Copy the Client ID and Client Secret
socialProviders: {
twitter: {
clientId: process.env.TWITTER_CLIENT_ID!,
clientSecret: process.env.TWITTER_CLIENT_SECRET!,
},
},Discord
- Go to the Discord Developer Portal
- Create a new application
- Go to OAuth2 settings
- Add redirect:
http://localhost:3000/api/auth/callback/discord - Copy the Client ID and reset the Client Secret
socialProviders: {
discord: {
clientId: process.env.DISCORD_CLIENT_ID!,
clientSecret: process.env.DISCORD_CLIENT_SECRET!,
},
},Spotify
- Go to the Spotify Developer Dashboard
- Create a new app
- Add redirect URI:
http://localhost:3000/api/auth/callback/spotify - Copy the Client ID and Client Secret
socialProviders: {
spotify: {
clientId: process.env.SPOTIFY_CLIENT_ID!,
clientSecret: process.env.SPOTIFY_CLIENT_SECRET!,
},
},Twitch
- Go to the Twitch Developer Console
- Register a new application
- Set OAuth Redirect URL:
http://localhost:3000/api/auth/callback/twitch - Copy the Client ID and generate a Client Secret
socialProviders: {
twitch: {
clientId: process.env.TWITCH_CLIENT_ID!,
clientSecret: process.env.TWITCH_CLIENT_SECRET!,
},
},- Go to the LinkedIn Developer Portal
- Create a new app
- Under Auth, add redirect URL:
http://localhost:3000/api/auth/callback/linkedin - Request the Sign In with LinkedIn using OpenID Connect product
- Copy the Client ID and Client Secret
socialProviders: {
linkedin: {
clientId: process.env.LINKEDIN_CLIENT_ID!,
clientSecret: process.env.LINKEDIN_CLIENT_SECRET!,
},
},Callback URL Pattern
All OAuth providers follow the same callback URL pattern:
{YOUR_SITE_URL}/api/auth/callback/{PROVIDER_ID}| Environment | Example |
|---|---|
| Local development | http://localhost:3000/api/auth/callback/github |
| Production | https://myapp.com/api/auth/callback/github |
The callback URL is handled by the catch-all route handler at /api/auth/[...all], which proxies the request to Convex where Better Auth processes the OAuth callback and creates/links the user account.
Client-Side Integration
Initiating Social Sign-In
import { authClient } from "@/lib/auth-client";
// Sign in with GitHub
await authClient.signIn.social({
provider: "github",
callbackURL: "/dashboard", // Where to redirect after success
});
// Sign in with Google
await authClient.signIn.social({
provider: "google",
callbackURL: "/dashboard",
});This redirects the user to the provider's OAuth consent page. After granting permission, they're redirected back to your callback URL, and then to the callbackURL you specified.
SocialButtons Component
Banata Auth ships a SocialButtons component that renders buttons for all configured providers:
import { SocialButtons } from "@banata-auth/react";
import { authClient } from "@/lib/auth-client";
// Define which providers to show
const providers = [
{ id: "google", label: "Google" },
{ id: "github", label: "GitHub" },
{ id: "apple", label: "Apple" },
];
export default function SignInPage() {
return (
<div>
<h1>Sign In</h1>
{/* Social login buttons */}
<SocialButtons
providers={providers}
authClient={authClient}
callbackURL="/dashboard"
/>
{/* Divider */}
<div>or</div>
{/* Email/password form */}
<SignInForm authClient={authClient} />
</div>
);
}The SocialProvider interface:
interface SocialProvider {
id: string; // Provider ID: "google", "github", etc.
label: string; // Display name: "Google", "GitHub", etc.
icon?: ReactNode; // Optional custom icon
}Conditional Provider Configuration
You can conditionally enable providers based on whether credentials are available:
socialProviders:
process.env.GITHUB_CLIENT_ID && process.env.GITHUB_CLIENT_SECRET
? {
github: {
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
},
...(process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET
? {
google: {
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
},
}
: {}),
}
: undefined,This way, if credentials aren't set, the provider is simply not available — no errors thrown.
Account Linking
When a user signs in with a social provider and an account with the same email already exists (from email/password sign-up), Better Auth handles account linking:
- Auto-link — If the email is verified on both sides, the social account is automatically linked to the existing user.
- New account — If the email doesn't match any existing user, a new user is created.
The user can then sign in with either method (email/password or social).
How the OAuth Flow Works
Here's what happens under the hood when a user clicks "Sign in with GitHub":
1. Browser → POST /api/auth/sign-in/social { provider: "github" }
2. Next.js route handler → Proxies to Convex .site/api/auth/sign-in/social
3. Convex (Better Auth) → Returns redirect to GitHub OAuth consent page
4. Browser → Redirected to github.com/login/oauth/authorize
5. User → Grants permission on GitHub
6. GitHub → Redirects to /api/auth/callback/github?code=xxx
7. Next.js route handler → Proxies to Convex .site/api/auth/callback/github
8. Convex (Better Auth) → Exchanges code for tokens, creates/links user, creates session
9. Browser → Redirected to callbackURL (/dashboard) with session cookie setThe reverse proxy at /api/auth/[...all] uses redirect: "manual" to properly handle the redirect chain without the browser prematurely resolving redirects.
Audit Events
Social sign-ins generate these audit log events:
| Event | When |
|---|---|
user.created | New user created via social sign-in |
session.created | Social sign-in successful (new session) |
account.linked | Social account linked to existing user |
Troubleshooting
"redirect_uri_mismatch" Error
The callback URL in your OAuth provider settings doesn't match the actual URL. Make sure:
- Development:
http://localhost:3000/api/auth/callback/{provider} - Production:
https://your-domain.com/api/auth/callback/{provider}
"Access Denied" After Granting Permission
- Check that the provider's Client Secret is correctly set on Convex:
npx convex env list - Check that the provider is included in your
socialProvidersconfig. - For Microsoft, make sure
tenantIdis set.
User Created Without Name/Email
Some providers don't return all user fields. Check the provider's scope settings to ensure you're requesting the right permissions (e.g., email scope for Google).
What's Next
- Magic Links — Passwordless authentication via email
- Multi-Factor Auth — Add TOTP as a second factor
- Organizations — Multi-tenant workspaces