Authlib 和 FastAPI OAuth2.0 Github


以下是使用 Authlib 和 FastAPI 实现 GitHub OAuth 登录的完整示例:

from authlib.integrations.starlette_client import OAuth
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse, RedirectResponse
from starlette.config import Config
from starlette.middleware.sessions import SessionMiddleware

# 配置
config = Config('.env')
oauth = OAuth(config)

CONF_URL = 'https://github.com/login/oauth/authorize'
GITHUB_CLIENT_ID = "你的GitHub Client ID"
GITHUB_CLIENT_SECRET = "你的GitHub Client Secret"

# 注册 GitHub OAuth
oauth.register(
    name='github',
    client_id=GITHUB_CLIENT_ID,
    client_secret=GITHUB_CLIENT_SECRET,
    access_token_url='https://github.com/login/oauth/access_token',
    access_token_params=None,
    authorize_url='https://github.com/login/oauth/authorize',
    authorize_params=None,
    api_base_url='https://api.github.com/',
    client_kwargs={'scope': 'user:email'},
)
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from .oauth import oauth

app = FastAPI()

# 添加 session 中间件
app.add_middleware(
    SessionMiddleware,
    secret_key="你的session密钥"
)

# CORS 配置
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000"],  # 前端域名
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

@app.get('/api/auth/login/github')
async def github_login(request: Request):
    # 生成用于防止CSRF攻击的state
    redirect_uri = request.url_for('github_callback')
    return await oauth.github.authorize_redirect(request, redirect_uri)

@app.get('/api/auth/callback/github')
async def github_callback(request: Request):
    try:
        token = await oauth.github.authorize_access_token(request)
        resp = await oauth.github.get('user', token=token)
        user = resp.json()
        
        # 获取用户邮箱
        emails_resp = await oauth.github.get('user/emails', token=token)
        emails = emails_resp.json()
        primary_email = next(
            (email['email'] for email in emails if email['primary']),
            None
        )
        
        # 构建用户信息
        user_data = {
            'id': user['id'],
            'login': user['login'],
            'name': user['name'],
            'email': primary_email,
            'avatar_url': user['avatar_url'],
            'access_token': token['access_token']
        }
        
        # 存储用户信息到session
        request.session['user'] = user_data
        
        # 重定向到前端
        frontend_url = "http://localhost:3000"  # 前端URL
        return RedirectResponse(
            url=f"{frontend_url}/auth/callback?token={token['access_token']}"
        )
        
    except Exception as e:
        return JSONResponse(
            status_code=400,
            content={'error': str(e)}
        )

@app.get('/api/auth/user')
async def get_user(request: Request):
    user = request.session.get('user')
    if not user:
        return JSONResponse(
            status_code=401,
            content={'error': 'Not authenticated'}
        )
    return user

@app.post('/api/auth/logout')
async def logout(request: Request):
    request.session.pop('user', None)
    return {'message': 'Successfully logged out'}

前端实现示例(React):

import React from 'react';

export const GithubLogin: React.FC = () => {
  const handleLogin = () => {
    // 打开新窗口进行 GitHub 授权
    const width = 600;
    const height = 600;
    const left = window.screen.width / 2 - width / 2;
    const top = window.screen.height / 2 - height / 2;
    
    window.open(
      '/api/auth/login/github',
      'GitHub Login',
      `width=${width},height=${height},top=${top},left=${left}`
    );
  };

  return (
    <button onClick={handleLogin}>
      使用 GitHub 登录
    </button>
  );
};
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';

export const AuthCallback: React.FC = () => {
  const navigate = useNavigate();
  
  useEffect(() => {
    const params = new URLSearchParams(window.location.search);
    const token = params.get('token');
    
    if (token) {
      // 存储 token
      localStorage.setItem('token', token);
      
      // 通知父窗口登录成功
      if (window.opener) {
        window.opener.postMessage({ type: 'AUTH_SUCCESS', token }, '*');
        window.close();
      } else {
        // 如果不是弹窗,则重定向
        navigate('/');
      }
    }
  }, [navigate]);

  return null;
};

环境变量配置 (.env):

GITHUB_CLIENT_ID=你的GitHub Client ID
GITHUB_CLIENT_SECRET=你的GitHub Client Secret
SECRET_KEY=你的session密钥

使用说明:

  1. 安装依赖:
pip install fastapi uvicorn authlib httpx python-dotenv python-jose[cryptography]
  1. 在 GitHub 开发者设置中:

    • 创建 OAuth App
    • 设置回调 URL 为 http://localhost:8000/api/auth/callback/github
    • 获取 Client ID 和 Client Secret
  2. 主要功能:

    • /api/auth/login/github: 开始 GitHub 登录流程
    • /api/auth/callback/github: GitHub 回调处理
    • /api/auth/user: 获取当前登录用户信息
    • /api/auth/logout: 登出
  3. 安全考虑:

    • 使用 session 存储用户信息
    • 实现了 CSRF 保护
    • 支持 CORS
    • 使用 HTTPS(生产环境)
  4. 启动服务:
uvicorn app.main:app --reload

这个实现:

  • 支持前后端分离
  • 处理了 GitHub OAuth 流程
  • 包含了错误处理
  • 提供了用户会话管理
  • 支持弹窗和重定向两种方式

声明:八零秘林|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - Authlib 和 FastAPI OAuth2.0 Github


记忆碎片 · 精神拾荒