OAuth Integration
Patterns for OAuth authentication with multiple providers.
Overview#
OAuth enables secure third-party authentication:
- Social login (Google, GitHub, Apple)
- Enterprise SSO integration
- Account linking across providers
- Custom OAuth provider support
Prerequisites:
- OAuth credentials from each provider
- NextAuth.js or custom implementation
Installation#
npm install next-auth1# .env.local
2GOOGLE_CLIENT_ID=your-google-client-id
3GOOGLE_CLIENT_SECRET=your-google-client-secret
4GITHUB_CLIENT_ID=your-github-client-id
5GITHUB_CLIENT_SECRET=your-github-client-secret
6APPLE_CLIENT_ID=your-apple-client-id
7APPLE_CLIENT_SECRET=your-apple-client-secretImplementation#
Multi-Provider Setup#
1// auth.ts
2import NextAuth from 'next-auth'
3import Google from 'next-auth/providers/google'
4import GitHub from 'next-auth/providers/github'
5import Apple from 'next-auth/providers/apple'
6import { PrismaAdapter } from '@auth/prisma-adapter'
7import { prisma } from '@/lib/db'
8
9export const { handlers, auth, signIn, signOut } = NextAuth({
10 adapter: PrismaAdapter(prisma),
11 providers: [
12 Google({
13 clientId: process.env.GOOGLE_CLIENT_ID!,
14 clientSecret: process.env.GOOGLE_CLIENT_SECRET!
15 }),
16 GitHub({
17 clientId: process.env.GITHUB_CLIENT_ID!,
18 clientSecret: process.env.GITHUB_CLIENT_SECRET!
19 }),
20 Apple({
21 clientId: process.env.APPLE_CLIENT_ID!,
22 clientSecret: process.env.APPLE_CLIENT_SECRET!
23 })
24 ],
25 callbacks: {
26 async signIn({ user, account, profile }) {
27 // Custom validation
28 if (account?.provider === 'google') {
29 return profile?.email_verified === true
30 }
31 return true
32 },
33 async session({ session, user }) {
34 session.user.id = user.id
35 return session
36 }
37 },
38 pages: {
39 signIn: '/login',
40 error: '/auth/error'
41 }
42})Social Login Buttons#
1// components/auth/SocialButtons.tsx
2'use client'
3
4import { signIn } from 'next-auth/react'
5import { Github } from 'lucide-react'
6
7export function SocialButtons() {
8 return (
9 <div className="space-y-3">
10 <button
11 onClick={() => signIn('google', { callbackUrl: '/dashboard' })}
12 className="flex w-full items-center justify-center gap-2 rounded-lg border py-2 hover:bg-gray-50"
13 >
14 <GoogleIcon className="h-5 w-5" />
15 Continue with Google
16 </button>
17
18 <button
19 onClick={() => signIn('github', { callbackUrl: '/dashboard' })}
20 className="flex w-full items-center justify-center gap-2 rounded-lg border py-2 hover:bg-gray-50"
21 >
22 <Github className="h-5 w-5" />
23 Continue with GitHub
24 </button>
25
26 <button
27 onClick={() => signIn('apple', { callbackUrl: '/dashboard' })}
28 className="flex w-full items-center justify-center gap-2 rounded-lg bg-black py-2 text-white"
29 >
30 <AppleIcon className="h-5 w-5" />
31 Continue with Apple
32 </button>
33 </div>
34 )
35}
36
37function GoogleIcon({ className }: { className?: string }) {
38 return (
39 <svg className={className} viewBox="0 0 24 24">
40 <path
41 fill="currentColor"
42 d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
43 />
44 <path
45 fill="currentColor"
46 d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
47 />
48 </svg>
49 )
50}Account Linking#
1// auth.ts
2export const { handlers, auth } = NextAuth({
3 callbacks: {
4 async signIn({ user, account }) {
5 // Check if user exists with different provider
6 const existingUser = await prisma.user.findUnique({
7 where: { email: user.email! },
8 include: { accounts: true }
9 })
10
11 if (existingUser) {
12 // Check if this provider is already linked
13 const hasProvider = existingUser.accounts.some(
14 a => a.provider === account?.provider
15 )
16
17 if (!hasProvider && account) {
18 // Link new provider to existing account
19 await prisma.account.create({
20 data: {
21 userId: existingUser.id,
22 type: account.type,
23 provider: account.provider,
24 providerAccountId: account.providerAccountId,
25 access_token: account.access_token,
26 refresh_token: account.refresh_token,
27 expires_at: account.expires_at
28 }
29 })
30 }
31 }
32
33 return true
34 }
35 }
36})Custom Provider#
1// lib/custom-provider.ts
2import type { OAuthConfig } from 'next-auth/providers'
3
4interface CustomProfile {
5 id: string
6 email: string
7 name: string
8 avatar_url: string
9}
10
11export function CustomProvider(): OAuthConfig<CustomProfile> {
12 return {
13 id: 'custom',
14 name: 'Custom Provider',
15 type: 'oauth',
16 authorization: {
17 url: 'https://custom.com/oauth/authorize',
18 params: { scope: 'user:email' }
19 },
20 token: 'https://custom.com/oauth/token',
21 userinfo: 'https://custom.com/api/user',
22 profile(profile) {
23 return {
24 id: profile.id,
25 email: profile.email,
26 name: profile.name,
27 image: profile.avatar_url
28 }
29 },
30 clientId: process.env.CUSTOM_CLIENT_ID,
31 clientSecret: process.env.CUSTOM_CLIENT_SECRET
32 }
33}OAuth Error Handling#
1// app/auth/error/page.tsx
2import Link from 'next/link'
3
4export default function AuthError({
5 searchParams
6}: {
7 searchParams: { error?: string }
8}) {
9 const errorMessages: Record<string, string> = {
10 OAuthSignin: 'Error starting OAuth flow',
11 OAuthCallback: 'Error in OAuth callback',
12 OAuthCreateAccount: 'Could not create account',
13 EmailCreateAccount: 'Could not create account',
14 Callback: 'Error in callback',
15 OAuthAccountNotLinked: 'Email already registered with different provider',
16 EmailSignin: 'Error sending email',
17 CredentialsSignin: 'Invalid credentials',
18 SessionRequired: 'Please sign in to continue',
19 default: 'An error occurred'
20 }
21
22 const message = errorMessages[searchParams.error ?? 'default']
23
24 return (
25 <div className="mx-auto max-w-md py-12 text-center">
26 <h1 className="mb-4 text-2xl font-bold">Authentication Error</h1>
27 <p className="mb-6 text-gray-600">{message}</p>
28 <Link href="/login" className="text-blue-600 hover:underline">
29 Try again
30 </Link>
31 </div>
32 )
33}Best Practices#
- Verify email addresses - Only allow verified emails from providers
- Handle account linking - Allow users to connect multiple providers
- Display friendly errors - Map OAuth error codes to user-friendly messages
- Store refresh tokens - Enable token refresh for long-lived access
- Configure redirect URIs - Set proper callback URLs in provider dashboards
Related Patterns#
- NextAuth.js - Full NextAuth.js configuration
- Session Management - Session handling
- MFA - Multi-factor authentication