diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 817762a..de2453a 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -32,13 +32,13 @@ jobs:
env: py311-fastapi84
- python: "3.7"
- env: py37-fastapi99
+ env: py37-fastapi100
- python: "3.9"
- env: py39-fastapi99
+ env: py39-fastapi100
- python: "3.10"
- env: py310-fastapi99
+ env: py310-fastapi100
- python: "3.11"
- env: py311-fastapi99
+ env: py311-fastapi100
steps:
- uses: actions/checkout@v2
diff --git a/README.md b/README.md
index ac8f71f..490cbe2 100644
--- a/README.md
+++ b/README.md
@@ -13,9 +13,7 @@ the [social-core](https://github.com/python-social-auth/social-core) authenticat
- Use multiple OAuth2 providers at the same time
* There need to be provided a way to configure the OAuth2 for multiple providers
-- Token -> user data, user data -> token easy conversion
- Customizable OAuth2 routes
-- Registration support
## Installation
@@ -43,12 +41,14 @@ middleware configuration is declared with the `OAuth2Config` and `OAuth2Client`
- `client_secret` - The OAuth2 client secret for the particular provider.
- `redirect_uri` - The OAuth2 redirect URI to redirect to after success. Defaults to the base URL.
- `scope` - The OAuth2 scope for the particular provider. Defaults to `[]`.
+- `claims` - Claims mapping for the certain provider.
It is also important to mention that for the configured clients of the auth providers, the authorization URLs are
accessible by the `/oauth2/{provider}/auth` path where the `provider` variable represents the exact value of the auth
provider backend `name` attribute.
```python
+from fastapi_oauth2.claims import Claims
from fastapi_oauth2.client import OAuth2Client
from fastapi_oauth2.config import OAuth2Config
from social_core.backends.github import GithubOAuth2
@@ -65,6 +65,10 @@ oauth2_config = OAuth2Config(
client_secret=os.getenv("OAUTH2_CLIENT_SECRET"),
redirect_uri="https://pysnippet.org/",
scope=["user:email"],
+ claims=Claims(
+ picture="avatar_url",
+ identity=lambda user: "%s:%s" % (user.get("provider"), user.get("id")),
+ ),
),
]
)
diff --git a/examples/demonstration/config.py b/examples/demonstration/config.py
index c63b136..935c2b1 100644
--- a/examples/demonstration/config.py
+++ b/examples/demonstration/config.py
@@ -3,6 +3,7 @@
from dotenv import load_dotenv
from social_core.backends.github import GithubOAuth2
+from fastapi_oauth2.claims import Claims
from fastapi_oauth2.client import OAuth2Client
from fastapi_oauth2.config import OAuth2Config
@@ -20,6 +21,10 @@
client_secret=os.getenv("OAUTH2_CLIENT_SECRET"),
# redirect_uri="http://127.0.0.1:8000/",
scope=["user:email"],
+ claims=Claims(
+ picture="avatar_url",
+ identity=lambda user: "%s:%s" % (user.get("provider"), user.get("id")),
+ ),
),
]
)
diff --git a/examples/demonstration/database.py b/examples/demonstration/database.py
new file mode 100644
index 0000000..a22915f
--- /dev/null
+++ b/examples/demonstration/database.py
@@ -0,0 +1,21 @@
+from sqlalchemy import create_engine
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import sessionmaker
+
+engine = create_engine(
+ "sqlite:///./database.sqlite",
+ connect_args={
+ "check_same_thread": False,
+ },
+)
+
+Base = declarative_base()
+SessionLocal = sessionmaker(bind=engine, autoflush=False)
+
+
+def get_db():
+ db = SessionLocal()
+ try:
+ yield db
+ finally:
+ db.close()
diff --git a/examples/demonstration/main.py b/examples/demonstration/main.py
index 1fd1291..e657bf1 100644
--- a/examples/demonstration/main.py
+++ b/examples/demonstration/main.py
@@ -1,14 +1,39 @@
from fastapi import APIRouter
from fastapi import FastAPI
+from sqlalchemy.orm import Session
from config import oauth2_config
+from database import Base
+from database import engine
+from database import get_db
+from fastapi_oauth2.middleware import Auth
from fastapi_oauth2.middleware import OAuth2Middleware
+from fastapi_oauth2.middleware import User
from fastapi_oauth2.router import router as oauth2_router
+from models import User as UserModel
from router import router as app_router
+Base.metadata.create_all(bind=engine)
+
router = APIRouter()
+
+async def on_auth(auth: Auth, user: User):
+ # perform a check for user existence in
+ # the database and create if not exists
+ db: Session = next(get_db())
+ query = db.query(UserModel)
+ if user.identity and not query.filter_by(identity=user.identity).first():
+ UserModel(**{
+ "identity": user.get("identity"),
+ "username": user.get("username"),
+ "image": user.get("image"),
+ "email": user.get("email"),
+ "name": user.get("name"),
+ }).save(db)
+
+
app = FastAPI()
app.include_router(app_router)
app.include_router(oauth2_router)
-app.add_middleware(OAuth2Middleware, config=oauth2_config)
+app.add_middleware(OAuth2Middleware, config=oauth2_config, callback=on_auth)
diff --git a/examples/demonstration/models.py b/examples/demonstration/models.py
new file mode 100644
index 0000000..ed86f45
--- /dev/null
+++ b/examples/demonstration/models.py
@@ -0,0 +1,27 @@
+from sqlalchemy import Column
+from sqlalchemy import Integer
+from sqlalchemy import String
+from sqlalchemy.orm import Session
+
+from database import Base
+
+
+class BaseModel(Base):
+ __abstract__ = True
+
+ def save(self, db: Session):
+ db.add(self)
+ db.commit()
+ db.refresh(self)
+ return self
+
+
+class User(BaseModel):
+ __tablename__ = "users"
+
+ id = Column(Integer, primary_key=True, index=True)
+ username = Column(String)
+ email = Column(String)
+ name = Column(String)
+ image = Column(String)
+ identity = Column(String, unique=True) # provider_name:user_id
diff --git a/examples/demonstration/router.py b/examples/demonstration/router.py
index fb8cf24..8656b1a 100644
--- a/examples/demonstration/router.py
+++ b/examples/demonstration/router.py
@@ -1,12 +1,16 @@
import json
+from fastapi import APIRouter
from fastapi import Depends
from fastapi import Request
-from fastapi import APIRouter
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
+from sqlalchemy.orm import Session
+from starlette.responses import RedirectResponse
+from database import get_db
from fastapi_oauth2.security import OAuth2
+from models import User
oauth2 = OAuth2()
router = APIRouter()
@@ -18,6 +22,39 @@ async def root(request: Request):
return templates.TemplateResponse("index.html", {"request": request, "user": request.user, "json": json})
+@router.get("/auth")
+def sim_auth(request: Request):
+ access_token = request.auth.jwt_create({
+ "id": 1,
+ "identity": "demo:1",
+ "image": None,
+ "display_name": "John Doe",
+ "email": "john.doe@auth.sim",
+ "username": "JohnDoe",
+ "exp": 3689609839,
+ })
+ response = RedirectResponse("/")
+ response.set_cookie(
+ "Authorization",
+ value=f"Bearer {access_token}",
+ max_age=request.auth.expires,
+ expires=request.auth.expires,
+ httponly=request.auth.http,
+ )
+ return response
+
+
@router.get("/user")
-def user(request: Request, _: str = Depends(oauth2)):
+def user_get(request: Request, _: str = Depends(oauth2)):
return request.user
+
+
+@router.get("/users")
+def users_get(request: Request, db: Session = Depends(get_db), _: str = Depends(oauth2)):
+ return db.query(User).all()
+
+
+@router.post("/users")
+async def users_post(request: Request, db: Session = Depends(get_db), _: str = Depends(oauth2)):
+ data = await request.json()
+ return User(**data).save(db)
diff --git a/examples/demonstration/templates/index.html b/examples/demonstration/templates/index.html
index c42adf1..9a8b81d 100644
--- a/examples/demonstration/templates/index.html
+++ b/examples/demonstration/templates/index.html
@@ -12,8 +12,15 @@
{% if request.user.is_authenticated %}
Sign out
-

+ {% if request.user.picture %}
+

+ {% else %}
+

+ {% endif %}
{% else %}
+
+ Simulate Login
+