Skip to content

Optimize the fastapi oauth2 integration #12

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 13, 2025
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 fastapi_oauth20/clients/feishu.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ def __init__(self, client_id: str, client_secret: str):
authorize_endpoint='https://passport.feishu.cn/suite/passport/oauth/authorize',
access_token_endpoint='https://passport.feishu.cn/suite/passport/oauth/token',
refresh_token_endpoint='https://passport.feishu.cn/suite/passport/oauth/authorize',
oauth_callback_route_name='feishu',
default_scopes=[
'contact:user.employee_id:readonly',
'contact:user.base:readonly',
Expand Down
1 change: 0 additions & 1 deletion fastapi_oauth20/clients/gitee.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ def __init__(self, client_id: str, client_secret: str):
authorize_endpoint='https://gitee.com/oauth/authorize',
access_token_endpoint='https://gitee.com/oauth/token',
refresh_token_endpoint='https://gitee.com/oauth/token',
oauth_callback_route_name='gitee',
default_scopes=['user_info'],
)

Expand Down
1 change: 0 additions & 1 deletion fastapi_oauth20/clients/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ def __init__(self, client_id: str, client_secret: str):
client_secret=client_secret,
authorize_endpoint='https://github.com/login/oauth/authorize',
access_token_endpoint='https://github.com/login/oauth/access_token',
oauth_callback_route_name='github',
default_scopes=['user', 'user:email'],
)

Expand Down
1 change: 0 additions & 1 deletion fastapi_oauth20/clients/google.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ def __init__(self, client_id: str, client_secret: str):
access_token_endpoint='https://oauth2.googleapis.com/token',
refresh_token_endpoint='https://oauth2.googleapis.com/token',
revoke_token_endpoint='https://accounts.google.com/o/oauth2/revoke',
oauth_callback_route_name='google',
default_scopes=['email', 'openid', 'profile'],
)

Expand Down
1 change: 0 additions & 1 deletion fastapi_oauth20/clients/linuxdo.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ def __init__(self, client_id: str, client_secret: str):
authorize_endpoint='https://connect.linux.do/oauth2/authorize',
access_token_endpoint='https://connect.linux.do/oauth2/token',
refresh_token_endpoint='https://connect.linux.do/oauth2/token',
oauth_callback_route_name='linuxdo',
token_endpoint_basic_auth=True,
)

Expand Down
1 change: 0 additions & 1 deletion fastapi_oauth20/clients/oschina.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ def __init__(self, client_id: str, client_secret: str):
authorize_endpoint='https://www.oschina.net/action/oauth2/authorize',
access_token_endpoint='https://www.oschina.net/action/openapi/token',
refresh_token_endpoint='https://www.oschina.net/action/openapi/token',
oauth_callback_route_name='oschina',
)

async def get_userinfo(self, access_token: str) -> dict:
Expand Down
6 changes: 3 additions & 3 deletions fastapi_oauth20/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import httpx


class FastAPIOAuth20BaseError(Exception):
"""The fastapi-oauth20 base error."""
class OAuth20BaseError(Exception):
"""The oauth2 base error."""

msg: str

Expand All @@ -13,7 +13,7 @@ def __init__(self, msg: str) -> None:
super().__init__(msg)


class OAuth20RequestError(FastAPIOAuth20BaseError):
class OAuth20RequestError(OAuth20BaseError):
"""OAuth2 httpx request error"""

def __init__(self, msg: str, response: httpx.Response | None = None) -> None:
Expand Down
70 changes: 57 additions & 13 deletions fastapi_oauth20/integrations/fastapi.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,80 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from fastapi import Request
from typing import Any

from fastapi_oauth20.errors import RedirectURIError
import httpx

from fastapi import HTTPException, Request

from fastapi_oauth20.errors import AccessTokenError, HTTPXOAuth20Error, OAuth20BaseError
from fastapi_oauth20.oauth20 import OAuth20Base


class OAuth20AuthorizeCallbackError(HTTPException, OAuth20BaseError):
"""The OAuth2 authorization callback error."""

def __init__(
self,
status_code: int,
detail: Any = None,
headers: dict[str, str] | None = None,
response: httpx.Response | None = None,
) -> None:
self.response = response
super().__init__(status_code=status_code, detail=detail, headers=headers)


class FastAPIOAuth20:
def __init__(
self,
client: OAuth20Base,
redirect_uri: str | None = None,
oauth_callback_route_name: str | None = None,
oauth2_callback_route_name: str | None = None,
):
"""
OAuth2 authorization callback dependency injection

:param client: A client base on OAuth20Base.
:param redirect_uri: OAuth2 callback full URL.
:param oauth2_callback_route_name: OAuth2 callback route name, as defined by the route decorator 'name' parameter.
"""
assert (redirect_uri is None and oauth2_callback_route_name is not None) or (
redirect_uri is not None and oauth2_callback_route_name is None
), 'FastAPIOAuth20 redirect_uri and oauth2_callback_route_name cannot be defined at the same time.'
self.client = client
self.oauth_callback_route_name = oauth_callback_route_name
self.redirect_uri = redirect_uri
self.oauth2_callback_route_name = oauth2_callback_route_name

async def __call__(
self,
request: Request,
code: str,
code: str | None = None,
state: str | None = None,
code_verifier: str | None = None,
error: str | None = None,
) -> tuple[dict, str]:
if self.redirect_uri is None:
if self.oauth_callback_route_name is None:
raise RedirectURIError('redirect_uri is required')
self.redirect_uri = str(request.url_for(self.oauth_callback_route_name))

access_token = await self.client.get_access_token(
code=code, redirect_uri=self.redirect_uri, code_verifier=code_verifier
)
if code is None or error is not None:
raise OAuth20AuthorizeCallbackError(
status_code=400,
detail=error if error is not None else None,
)

if self.oauth2_callback_route_name:
redirect_url = str(request.url_for(self.oauth2_callback_route_name))
else:
redirect_url = self.redirect_uri

try:
access_token = await self.client.get_access_token(
code=code,
redirect_uri=redirect_url,
code_verifier=code_verifier,
)
except (HTTPXOAuth20Error, AccessTokenError) as e:
raise OAuth20AuthorizeCallbackError(
status_code=500,
detail=e.msg,
response=e.response,
) from e

return access_token, state
3 changes: 0 additions & 3 deletions fastapi_oauth20/oauth20.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ def __init__(
access_token_endpoint: str,
refresh_token_endpoint: str | None = None,
revoke_token_endpoint: str | None = None,
oauth_callback_route_name: str = 'oauth20',
default_scopes: list[str] | None = None,
token_endpoint_basic_auth: bool = False,
revoke_token_endpoint_basic_auth: bool = False,
Expand All @@ -41,7 +40,6 @@ def __init__(
:param access_token_endpoint: The access token endpoint URL.
:param refresh_token_endpoint: The refresh token endpoint URL.
:param revoke_token_endpoint: The revoke token endpoint URL.
:param oauth_callback_route_name:
:param default_scopes:
:param token_endpoint_basic_auth:
:param revoke_token_endpoint_basic_auth:
Expand All @@ -52,7 +50,6 @@ def __init__(
self.access_token_endpoint = access_token_endpoint
self.refresh_token_endpoint = refresh_token_endpoint
self.revoke_token_endpoint = revoke_token_endpoint
self.oauth_callback_route_name = oauth_callback_route_name
self.default_scopes = default_scopes
self.token_endpoint_basic_auth = token_endpoint_basic_auth
self.revoke_token_endpoint_basic_auth = revoke_token_endpoint_basic_auth
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[project]
name = "fastapi_oauth20"
name = "fastapi-oauth20"
description = "在 FastAPI 中异步授权 OAuth 2.0 客户端"
dynamic = [
"version",
Expand Down