Supabase OAuth社交登录 #

一、OAuth概述 #

1.1 OAuth流程 #

text
OAuth登录流程
├── 1. 用户点击第三方登录按钮
├── 2. 跳转到OAuth提供商授权页面
├── 3. 用户授权应用访问
├── 4. OAuth提供商返回授权码
├── 5. Supabase交换访问令牌
├── 6. 创建/登录用户
└── 7. 返回会话信息

1.2 支持的OAuth提供商 #

提供商 Provider ID 说明
GitHub github 开发者常用
Google google 通用社交登录
Apple apple iOS应用必需
Facebook facebook 社交应用
Twitter twitter 社交应用
Discord discord 游戏社区
Spotify spotify 音乐应用
Slack slack 企业应用
LinkedIn linkedin 职业社交
Twitch twitch 直播平台

二、配置OAuth #

2.1 Dashboard配置 #

text
Dashboard > Authentication > Providers

配置步骤
├── 1. 选择要启用的提供商
├── 2. 获取OAuth应用凭证
├── 3. 填写Client ID和Client Secret
├── 4. 配置回调URL
└── 5. 保存配置

2.2 GitHub OAuth配置 #

text
1. 访问 GitHub Settings > Developer settings > OAuth Apps
2. 创建新的OAuth App
   ├── Application name: Your App Name
   ├── Homepage URL: https://your-app.com
   └── Authorization callback URL: 
       https://<project-ref>.supabase.co/auth/v1/callback
3. 获取Client ID
4. 生成Client Secret
5. 在Supabase Dashboard中配置

2.3 Google OAuth配置 #

text
1. 访问 Google Cloud Console
2. 创建OAuth 2.0客户端ID
   ├── 应用类型: Web应用
   ├── 已授权的JavaScript来源: https://your-app.com
   └── 已授权的重定向URI:
       https://<project-ref>.supabase.co/auth/v1/callback
3. 获取客户端ID和密钥
4. 在Supabase Dashboard中配置

三、实现OAuth登录 #

3.1 基础OAuth登录 #

typescript
// GitHub登录
const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'github',
})

// 用户将被重定向到GitHub授权页面

3.2 带配置的OAuth登录 #

typescript
// 带回调URL的登录
const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'github',
  options: {
    redirectTo: 'https://example.com/auth/callback',
    scopes: 'repo gist',  // 请求的权限范围
  },
})

3.3 获取用户信息 #

typescript
// OAuth登录后获取用户信息
const { data: { user } } = await supabase.auth.getUser()

// 用户元数据包含OAuth信息
console.log(user?.user_metadata)
// {
//   avatar_url: 'https://avatars.githubusercontent.com/u/...',
//   full_name: 'John Doe',
//   user_name: 'johndoe',
//   provider_id: '12345678',
//   ...
// }

四、处理OAuth回调 #

4.1 回调页面 #

typescript
// auth/callback.tsx
import { useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import { supabase } from '../lib/supabase'

export function AuthCallback() {
  const navigate = useNavigate()

  useEffect(() => {
    // Supabase自动处理OAuth回调
    supabase.auth.getSession().then(({ data: { session } }) => {
      if (session) {
        navigate('/dashboard')
      } else {
        navigate('/login')
      }
    })
  }, [navigate])

  return <div>Processing login...</div>
}

4.2 Next.js App Router处理 #

typescript
// app/auth/callback/route.ts
import { createClient } from '@/lib/supabase/server'
import { NextResponse } from 'next/server'

export async function GET(request: Request) {
  const requestUrl = new URL(request.url)
  const code = requestUrl.searchParams.get('code')
  const next = requestUrl.searchParams.get('next') || '/'

  if (code) {
    const supabase = await createClient()
    await supabase.auth.exchangeCodeForSession(code)
  }

  return NextResponse.redirect(requestUrl.origin + next)
}

五、OAuth登录组件 #

5.1 通用OAuth按钮组件 #

tsx
import { supabase } from '../lib/supabase'

interface OAuthButtonProps {
  provider: 'github' | 'google' | 'apple' | 'facebook' | 'twitter'
  redirectTo?: string
  children: React.ReactNode
}

export function OAuthButton({ 
  provider, 
  redirectTo,
  children 
}: OAuthButtonProps) {
  const handleLogin = async () => {
    const { error } = await supabase.auth.signInWithOAuth({
      provider,
      options: {
        redirectTo: redirectTo || window.location.origin + '/auth/callback',
      },
    })

    if (error) {
      console.error('OAuth error:', error)
    }
  }

  return (
    <button onClick={handleLogin} className="oauth-button">
      {children}
    </button>
  )
}

5.2 使用示例 #

tsx
export function LoginPage() {
  return (
    <div className="login-page">
      <h1>Sign in with</h1>
      
      <OAuthButton provider="github">
        <GitHubIcon /> Continue with GitHub
      </OAuthButton>
      
      <OAuthButton provider="google">
        <GoogleIcon /> Continue with Google
      </OAuthButton>
      
      <OAuthButton provider="apple">
        <AppleIcon /> Continue with Apple
      </OAuthButton>
    </div>
  )
}

六、链接OAuth账号 #

6.1 链接新提供商 #

typescript
// 将OAuth账号链接到当前用户
const { data, error } = await supabase.auth.linkIdentity({
  provider: 'github',
})

// 用户可以同时使用多种方式登录

6.2 解除链接 #

typescript
// 获取用户的身份
const { data: { identities } } = await supabase.auth.getUserIdentities()

// 解除链接
const { error } = await supabase.auth.unlinkIdentity(identities[0])

6.3 检查已链接账号 #

typescript
// 获取用户所有身份
const { data: { identities }, error } = await supabase.auth.getUserIdentities()

identities?.forEach(identity => {
  console.log('Provider:', identity.provider)
  console.log('Email:', identity.identity_data?.email)
})

七、自定义Scopes #

7.1 GitHub Scopes #

typescript
// 请求GitHub特定权限
const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'github',
  options: {
    scopes: 'repo user email',
  },
})

7.2 Google Scopes #

typescript
// 请求Google特定权限
const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'google',
  options: {
    scopes: 'https://www.googleapis.com/auth/calendar',
  },
})

八、PKCE流程 #

8.1 启用PKCE #

typescript
// 使用PKCE增强安全性
const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'github',
  options: {
    skipBrowserRedirect: true,  // 手动处理重定向
  },
})

if (data?.url) {
  // 在新窗口打开
  window.open(data.url, '_blank')
}

8.2 处理PKCE回调 #

typescript
// PKCE回调处理
useEffect(() => {
  const hash = window.location.hash
  if (hash) {
    supabase.auth.getSession().then(({ data: { session } }) => {
      if (session) {
        // 登录成功
      }
    })
  }
}, [])

九、移动端OAuth #

9.1 React Native #

typescript
import * as WebBrowser from 'expo-web-browser'
import * as Linking from 'expo-linking'
import { makeRedirectUri } from 'expo-auth-session'

// 完成OAuth会话
WebBrowser.maybeCompleteAuthSession()

// 生成重定向URI
const redirectUrl = makeRedirectUri({
  scheme: 'myapp',
  path: 'auth/callback',
})

// 发起OAuth登录
const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'github',
  options: {
    redirectTo: redirectUrl,
    skipBrowserRedirect: true,
  },
})

if (data?.url) {
  const result = await WebBrowser.openAuthSessionAsync(
    data.url,
    redirectUrl
  )
  
  if (result.type === 'success') {
    // 处理回调
  }
}

9.2 iOS配置 #

xml
<!-- Info.plist -->
<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>myapp</string>
        </array>
    </dict>
</array>

9.3 Android配置 #

xml
<!-- AndroidManifest.xml -->
<intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="myapp" />
</intent-filter>

十、服务端验证 #

10.1 验证OAuth用户 #

typescript
// 服务端获取用户信息
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(url, serviceRoleKey, {
  auth: {
    autoRefreshToken: false,
    persistSession: false,
  },
})

// 通过access token获取用户
const { data: { user }, error } = await supabase.auth.getUser(accessToken)

if (user) {
  // 验证OAuth提供商
  const identities = user.identities || []
  const hasGitHub = identities.some(i => i.provider === 'github')
}

十一、错误处理 #

11.1 常见错误 #

typescript
// 错误处理
const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'github',
})

if (error) {
  switch (error.message) {
    case 'provider is not enabled':
      console.error('该OAuth提供商未启用')
      break
    case 'invalid_callback_url':
      console.error('回调URL配置错误')
      break
    case 'access_denied':
      console.error('用户拒绝授权')
      break
    default:
      console.error('OAuth错误:', error.message)
  }
}

十二、最佳实践 #

12.1 安全建议 #

text
OAuth安全建议
├── 使用HTTPS
├── 配置正确的回调URL
├── 使用PKCE流程
├── 验证state参数
├── 最小权限原则
└── 定期审查授权

12.2 用户体验优化 #

typescript
// 保存登录前的页面
const handleOAuthLogin = async (provider: string) => {
  // 保存当前路径
  localStorage.setItem('redirectAfterLogin', window.location.pathname)
  
  await supabase.auth.signInWithOAuth({
    provider,
    options: {
      redirectTo: window.location.origin + '/auth/callback',
    },
  })
}

// 登录后跳转
const afterLogin = () => {
  const redirect = localStorage.getItem('redirectAfterLogin') || '/dashboard'
  localStorage.removeItem('redirectAfterLogin')
  navigate(redirect)
}

十三、总结 #

OAuth登录要点:

操作 方法
登录 signInWithOAuth({ provider })
链接 linkIdentity({ provider })
解除 unlinkIdentity(identity)
获取身份 getUserIdentities()

下一步,让我们学习魔法链接登录!

最后更新:2026-03-28