Skip to content

Commit ebc6504

Browse files
authored
Add FastAPI Support (#73)
* Add Fastapi support * Add Fastapi example * Add request parameter to handle_authorize callback * remove unnecessary directory in .gitignore
1 parent a60b246 commit ebc6504

File tree

5 files changed

+186
-0
lines changed

5 files changed

+186
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ venv/
2121
*.sqlite3
2222

2323
flask_example/config.py
24+
fastapi_example/.env

fastapi_example/app.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from authlib.integrations.starlette_client import OAuth
2+
from fastapi.responses import HTMLResponse
3+
from starlette.config import Config
4+
from starlette.middleware.sessions import SessionMiddleware
5+
from loginpass import create_fastapi_routes, Twitter, GitHub, Google
6+
7+
from fastapi import FastAPI
8+
9+
app = FastAPI()
10+
11+
config = Config(".env")
12+
oauth = OAuth(config)
13+
14+
app.add_middleware(SessionMiddleware, secret_key=config.get("SECRET_KEY"))
15+
16+
backends = [Twitter, GitHub, Google]
17+
18+
19+
@app.get("/", response_class=HTMLResponse)
20+
async def root() -> str:
21+
tpl = '<li><a href="/login/{}">{}</a></li>'
22+
lis = [tpl.format(b.NAME, b.NAME) for b in backends]
23+
return "<ul>{}</ul>".format("".join(lis))
24+
25+
26+
def handle_authorize(remote, token, user_info, request):
27+
return user_info
28+
29+
30+
router = create_fastapi_routes(backends, oauth, handle_authorize)
31+
32+
app.include_router(router)

fastapi_example/example.env

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
SECRET_KEY=secret
2+
3+
TWITTER_CLIENT_ID=
4+
TWITTER_CLIENT_SECRET=
5+
6+
FACEBOOK_CLIENT_ID=
7+
FACEBOOK_CLIENT_SECRET=
8+
9+
GOOGLE_CLIENT_ID=
10+
GOOGLE_CLIENT_SECRET=
11+
12+
GITHUB_CLIENT_ID=
13+
GITHUB_CLIENT_SECRET=
14+
15+
DROPBOX_CLIENT_ID=
16+
DROPBOX_CLIENT_SECRET=
17+
18+
REDDIT_CLIENT_ID=
19+
REDDIT_CLIENT_SECRET=
20+
21+
GITLAB_CLIENT_ID=
22+
GITLAB_CLIENT_SECRET=
23+
24+
SLACK_CLIENT_ID=
25+
SLACK_CLIENT_SECRET=
26+
27+
DISCORD_CLIENT_ID=
28+
DISCORD_CLIENT_SECRET=
29+
30+
STACKOVERFLOW_CLIENT_ID=
31+
STACKOVERFLOW_CLIENT_SECRET=
32+
# STACKOVERFLOW_CLIENT_KWARGS={
33+
# 'api_key': '', # required
34+
# 'api_filter': '' # optional
35+
# }
36+
37+
BITBUCKET_CLIENT_ID=
38+
BITBUCKET_CLIENT_SECRET=
39+
40+
STRAVA_CLIENT_ID=
41+
STRAVA_CLIENT_SECRET=
42+
43+
SPOTIFY_CLIENT_ID=
44+
SPOTIFY_CLIENT_SECRET=
45+
46+
YANDEX_CLIENT_ID=
47+
YANDEX_CLIENT_SECRET=
48+
49+
TWITCH_CLIENT_ID=
50+
TWITCH_CLIENT_SECRET=
51+
52+
VK_CLIENT_ID=
53+
VK_CLIENT_SECRET=
54+
55+
BATTLENET_CLIENT_ID=
56+
BATTLENET_CLIENT_SECRET=

loginpass/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from ._flask import create_flask_blueprint
2+
from ._fastapi import create_fastapi_routes
23
from ._django import create_django_urlpatterns
34
from ._consts import version, homepage
45
from .azure import Azure, create_azure_backend
@@ -27,12 +28,14 @@
2728

2829
__all__ = [
2930
'create_flask_blueprint',
31+
'create_fastapi_routes',
3032
'create_django_urlpatterns',
3133
'Azure', 'create_azure_backend',
3234
'BattleNet', 'create_battlenet_backend',
3335
'Google', 'GoogleServiceAccount',
3436
'GitHub',
3537
'Facebook',
38+
'Instagram',
3639
'Twitter',
3740
'Dropbox',
3841
'LinkedIn',

loginpass/_fastapi.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
2+
def create_fastapi_routes(backends, oauth, handle_authorize):
3+
"""Create a Fastapi routes that you can register it directly to fastapi
4+
app. The routes contains two route: ``/auth/<backend>`` and
5+
``/login/<backend>``::
6+
7+
from authlib.integrations.starlette_client import OAuth
8+
from fastapi.responses import HTMLResponse
9+
from starlette.config import Config
10+
from starlette.middleware.sessions import SessionMiddleware
11+
from loginpass import create_fastapi_routes, Twitter, GitHub, Google
12+
13+
from fastapi import FastAPI
14+
15+
app = FastAPI()
16+
config = Config(".env")
17+
oauth = OAuth(config)
18+
19+
app.add_middleware(SessionMiddleware, secret_key=config.get("SECRET_KEY"))
20+
21+
def handle_authorize(remote, token, user_info, request):
22+
return user_info
23+
24+
router = create_fastapi_routes([GitHub, Google], oauth, handle_authorize)
25+
app.include_router(router, prefix="/account")
26+
27+
# visit /account/login/github
28+
# callback /account/auth/github
29+
30+
:param backends: A list of configured backends
31+
:param oauth: Authlib Flask OAuth instance
32+
:param handle_authorize: A function to handle authorized response
33+
:return: Fastapi APIRouter instance
34+
"""
35+
from fastapi import Request, APIRouter
36+
from fastapi.exceptions import HTTPException
37+
38+
router = APIRouter()
39+
40+
for b in backends:
41+
register_to(oauth, b)
42+
43+
@router.get("/auth/{backend}")
44+
async def auth(
45+
backend: str,
46+
id_token: str = None,
47+
code: str = None,
48+
oauth_verifier: str = None,
49+
request: Request = None,
50+
):
51+
remote = oauth.create_client(backend)
52+
if remote is None:
53+
raise HTTPException(404)
54+
55+
if code:
56+
token = await remote.authorize_access_token(request)
57+
if id_token:
58+
token["id_token"] = id_token
59+
elif id_token:
60+
token = {"id_token": id_token}
61+
elif oauth_verifier:
62+
# OAuth 1
63+
token = await remote.authorize_access_token(request)
64+
else:
65+
# handle failed
66+
return handle_authorize(remote, None, None)
67+
if "id_token" in token:
68+
user_info = await remote.parse_id_token(request, token)
69+
else:
70+
remote.token = token
71+
user_info = await remote.userinfo(token=token)
72+
return handle_authorize(remote, token, user_info, request)
73+
74+
@router.get("/login/{backend}")
75+
async def login(backend: str, request: Request):
76+
remote = oauth.create_client(backend)
77+
if remote is None:
78+
raise HTTPException(404)
79+
80+
redirect_uri = request.url_for("auth", backend="google")
81+
conf_key = "{}_AUTHORIZE_PARAMS".format(backend.upper())
82+
params = oauth.config.get(conf_key, default={})
83+
return await remote.authorize_redirect(request, redirect_uri, **params)
84+
85+
return router
86+
87+
88+
def register_to(oauth, backend_cls):
89+
from authlib.integrations.starlette_client import StarletteRemoteApp
90+
91+
class RemoteApp(backend_cls, StarletteRemoteApp):
92+
OAUTH_APP_CONFIG = backend_cls.OAUTH_CONFIG
93+
94+
oauth.register(RemoteApp.NAME, overwrite=True, client_cls=RemoteApp)

0 commit comments

Comments
 (0)