Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion docs/advanced.md

This file was deleted.

2 changes: 1 addition & 1 deletion docs/explanation.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
code 等参数
- ==state== 用于在请求和回调之间维护状态,主要用于防止跨站请求伪造(CSRF)攻击
- ==source== 支持的第三方客户端,比如:GitHub、LinuxDo 等
- ==sid== 第三方客户端的用户 ID。以下是关于各平台的 sid 存储逻辑:
- ==sid== 第三方客户端的用户 ID

!!! warning

Expand Down
63 changes: 51 additions & 12 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,61 @@
<div align="center">
<h1>🔐</h1>
</div>
# FastAPI OAuth 2.0

---
在 FastAPI 中异步授权 OAuth 2.0 客户端

**Documentation
**: <a href="https://fastapi-practices.github.io/fastapi-oauth20" target="_blank">https://fastapi-practices.github.io/fastapi-oauth20</a>
## Features

**Source Code
**: <a href="https://github.com/fastapi-practices/fastapi-oauth20" target="_blank">https://github.com/fastapi-practices/fastapi-oauth20</a>
- **异步支持** - 使用 async/await 构建以获得最佳性能
- **令牌管理** - 内置令牌刷新和吊销
- **FastAPI 集成** - 用于回调处理的无缝依赖注入
- **类型安全** - 完整类型提示
- **错误处理** - OAuth2 错误的综合异常层次结构

---
## Quick Start

在 FastAPI 中异步授权 OAuth 2.0 客户端
### Installation

```bash
pip install fastapi-oauth20
```

### Basic Usage

```python
from fastapi import FastAPI, Depends
from fastapi_oauth20 import GitHubOAuth20, FastAPIOAuth20
from fastapi.responses import RedirectResponse
import secrets

app = FastAPI()

# 定义重定向地址
redirect_uri = "http://localhost:8000/auth/github/callback"

# 初始化 GitHub OAuth2 客户端
github_client = GitHubOAuth20(
client_id="your_github_client_id",
client_secret="your_github_client_secret"
)

# 创建 FastAPI OAuth2 依赖项
github_oauth = FastAPIOAuth20(
client=github_client,
redirect_uri=redirect_uri
)


@app.get("/auth/github")
async def github_auth():
auth_url = await github_client.get_authorization_url(redirect_uri=redirect_uri)
return RedirectResponse(url=auth_url)

我们的目标是集成多个 CN 第三方客户端,敬请期待(🐦)...

你可以在 [客户端状态](status.md) 获取当前集成情况
@app.get("/auth/github/callback")
async def github_callback(oauth_result: tuple = Depends(github_oauth)):
token_data, state = oauth_result
user_info = await github_client.get_userinfo(token_data["access_token"])
return {"user": user_info}
```

## 互动

Expand Down
6 changes: 0 additions & 6 deletions docs/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,3 @@
```sh
uv add fastapi-oauth20
```

=== ":simple-pdm: pdm"

```sh
pdm add fastapi-oauth20
```
2 changes: 1 addition & 1 deletion docs/status.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

对于强制要求【实名 + 人脸认证】的平台,植入变得困难,所以它们不会很快到来

## END
## FINISHED

- [x] [LinuxDo](clients/linuxdo.md)
- [x] [GitHub](clients/github.md)
Expand Down
279 changes: 278 additions & 1 deletion docs/usage.md
Original file line number Diff line number Diff line change
@@ -1 +1,278 @@
TODO...
# 使用指南

本指南介绍如何将 FastAPI OAuth2.0 库与各种 OAuth2 提供程序一起使用。

## 基本用法

### 1. 选择 OAuth2 提供商并初始化客户端

```python
from fastapi_oauth20 import GitHubOAuth20, GoogleOAuth20, FastAPIOAuth20
from fastapi import FastAPI, Depends
from fastapi.responses import RedirectResponse
import secrets

app = FastAPI()

# 初始化 GitHub OAuth2 客户端
github_client = GitHubOAuth20(
client_id="your_github_client_id",
client_secret="your_github_client_secret"
)

# 初始化 Google OAuth2 客户端
google_client = GoogleOAuth20(
client_id="your_google_client_id",
client_secret="your_google_client_secret"
)
```

### 2. 创建 FastAPI OAuth2 依赖

```python
# 创建 FastAPI OAuth2 依赖
github_oauth = FastAPIOAuth20(
client=github_client,
redirect_uri="http://localhost:8000/auth/github/callback"
)

google_oauth = FastAPIOAuth20(
client=google_client,
redirect_uri="http://localhost:8000/auth/google/callback"
)
```

### 3. 定义授权端点

```python
@app.get("/auth/{provider}")
async def auth_provider(provider: str):
"""重定向用户到 OAuth2 提供商进行授权"""

# 生成安全的 state 参数用于 CSRF 保护
state = secrets.token_urlsafe(32)

if provider == "github":
auth_url = await github_client.get_authorization_url(
redirect_uri="http://localhost:8000/auth/github/callback",
state=state
)
elif provider == "google":
auth_url = await google_client.get_authorization_url(
redirect_uri="http://localhost:8000/auth/google/callback",
state=state
)
else:
raise HTTPException(status_code=404, detail="不支持的提供商")

return RedirectResponse(url=auth_url)
```

### 4. 处理 OAuth 回调

```python
from typing import Tuple, Dict, Any


@app.get("/auth/github/callback")
async def github_callback(
oauth_result: Tuple[Dict[str, Any], str] = Depends(github_oauth)
):
"""处理 GitHub OAuth 回调"""
token_data, state = oauth_result

# 获取用户信息
user_info = await github_client.get_userinfo(token_data["access_token"])

return {
"user": user_info,
"access_token": token_data["access_token"],
"state": state
}


@app.get("/auth/google/callback")
async def google_callback(
oauth_result: Tuple[Dict[str, Any], str] = Depends(google_oauth)
):
"""处理 Google OAuth 回调"""
token_data, state = oauth_result

# 获取用户信息
user_info = await google_client.get_userinfo(token_data["access_token"])

return {
"user": user_info,
"access_token": token_data["access_token"],
"state": state
}
```

## 高级用法

### PKCE (Proof Key for Code Exchange)

对于公共客户端(移动应用、SPA),使用 PKCE 增强安全性:

```python
import base64
import hashlib
import secrets


def generate_pkce_challenge():
"""生成 PKCE 代码验证器和挑战码"""
code_verifier = base64.urlsafe_b64encode(secrets.token_bytes(32)).decode('utf-8').rstrip('=')
code_challenge = base64.urlsafe_b64encode(
hashlib.sha256(code_verifier.encode('utf-8')).digest()
).decode('utf-8').rstrip('=')
return code_verifier, code_challenge


@app.get("/auth/github/pkce")
async def github_auth_pkce():
"""GitHub OAuth with PKCE"""
code_verifier, code_challenge = generate_pkce_challenge()
state = secrets.token_urlsafe(32)

# 在实际应用中,应该将 code_verifier 和 state 存储在会话或数据库中
# 这里为了演示目的简化处理

auth_url = await github_client.get_authorization_url(
redirect_uri="http://localhost:8000/auth/github/callback",
state=state,
code_challenge=code_challenge,
code_challenge_method="S256"
)

return RedirectResponse(url=auth_url)
```

### 令牌刷新

某些提供商支持刷新令牌来延长会话:

```python
@app.post("/auth/refresh")
async def refresh_token(refresh_token: str, provider: str):
"""使用刷新令牌获取新的访问令牌"""

if provider == "github":
# GitHub 不支持 OAuth 刷新令牌
raise HTTPException(status_code=400, detail="GitHub 不支持令牌刷新")
elif provider == "google":
try:
new_tokens = await google_client.refresh_token(refresh_token)
return {"access_token": new_tokens["access_token"]}
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
```

### 令牌撤销

用户登出时撤销令牌:

```python
@app.post("/auth/revoke")
async def revoke_token(access_token: str, provider: str):
"""撤销访问令牌"""

if provider == "google":
try:
await google_client.revoke_token(access_token)
return {"message": "令牌撤销成功"}
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
else:
raise HTTPException(status_code=400, detail="该提供商不支持令牌撤销")
```

## 错误处理

库提供了全面的错误处理:

```python
from fastapi_oauth20.errors import (
OAuth20AuthorizeCallbackError,
AccessTokenError,
GetUserInfoError
)


@app.exception_handler(OAuth20AuthorizeCallbackError)
async def oauth_callback_error_handler(request: Request, exc: OAuth20AuthorizeCallbackError):
"""处理 OAuth 回调错误"""
return {
"error": "OAuth 授权失败",
"detail": exc.detail,
"status_code": exc.status_code
}


@app.exception_handler(AccessTokenError)
async def access_token_error_handler(request: Request, exc: AccessTokenError):
"""处理访问令牌错误"""
return {
"error": "访问令牌交换失败",
"detail": exc.msg
}
```

## 完整示例

```python
from fastapi import FastAPI, Depends, HTTPException
from fastapi.responses import RedirectResponse
from fastapi_oauth20 import GitHubOAuth20, FastAPIOAuth20
from fastapi_oauth20.errors import OAuth20AuthorizeCallbackError
import secrets

app = FastAPI()

# 初始化客户端
github_client = GitHubOAuth20(
client_id="your_client_id",
client_secret="your_client_secret"
)

# 创建依赖
github_oauth = FastAPIOAuth20(
client=github_client,
redirect_uri="http://localhost:8000/auth/github/callback"
)


@app.get("/auth/github")
async def github_auth():
"""GitHub 授权入口"""
state = secrets.token_urlsafe(32)
auth_url = await github_client.get_authorization_url(
redirect_uri="http://localhost:8000/auth/github/callback",
state=state
)
return RedirectResponse(url=auth_url)


@app.get("/auth/github/callback")
async def github_callback(
oauth_result: tuple = Depends(github_oauth)
):
"""GitHub 授权回调"""
token_data, state = oauth_result
user_info = await github_client.get_userinfo(token_data["access_token"])
return {"user": user_info}


# 错误处理
@app.exception_handler(OAuth20AuthorizeCallbackError)
async def oauth_error_handler(request, exc):
return {"error": "授权失败", "detail": exc.detail}
```

## 注意事项

1. **安全性**: 始终使用 HTTPS 端点
2. **状态管理**: 使用安全的 state 参数防止 CSRF 攻击
3. **令牌存储**: 安全地存储访问令牌和刷新令牌
4. **错误处理**: 妥善处理各种 OAuth2 错误场景
5. **作用域**: 只请求必要的作用域,遵循最小权限原则
1 change: 1 addition & 0 deletions fastapi_oauth20/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@
from .clients.linuxdo import LinuxDoOAuth20 as LinuxDoOAuth20
from .clients.oschina import OSChinaOAuth20 as OSChinaOAuth20
from .integrations.fastapi import FastAPIOAuth20 as FastAPIOAuth20
from .integrations.fastapi import OAuth20AuthorizeCallbackError as OAuth20AuthorizeCallbackError

__version__ = '0.0.1'
Loading