+
+ + +
+
+
+ +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+ + + +

OAuth2

+

FastAPI Users provides an optional OAuth2 authentication support. It relies on HTTPX OAuth library, which is a pure-async implementation of OAuth2.

+

Installation

+

You should install the library with the optional dependencies for OAuth:

+
pip install fastapi-users[sqlalchemy,oauth]
+
+ +
pip install fastapi-users[mongodb,oauth]
+
+ +
pip install fastapi-users[tortoise-orm,oauth]
+
+ +

Configuration

+

Instantiate an OAuth2 client

+

You first need to get an HTTPX OAuth client instance. Read the documentation for more information.

+
from httpx_oauth.clients.google import GoogleOAuth2
+
+google_oauth_client = GoogleOAuth2("CLIENT_ID", "CLIENT_SECRET")
+
+ +

Setup the models

+

The user models differ a bit from the standard one as we have to have a way to store the OAuth information (access tokens, account ids...).

+
from fastapi_users import models
+
+
+class User(models.BaseUser, models.BaseOAuthAccountMixin):
+    pass
+
+
+class UserCreate(User, models.BaseUserCreate):
+    pass
+
+
+class UserUpdate(User, models.BaseUserUpdate):
+    pass
+
+
+class UserDB(User, models.BaseUserDB):
+    pass
+
+ +

Notice that we inherit from the BaseOAuthAccountMixin, which adds a List of BaseOAuthAccount objects. This object is structured like this:

+
    +
  • id (str) – Unique identifier of the user. Default to a UUID4.
  • +
  • oauth_name (str) – Name of the OAuth service. It corresponds to the name property of the OAuth client.
  • +
  • access_token (str) – Access token.
  • +
  • expires_at (int) - Timestamp at which the access token is expired.
  • +
  • refresh_token (Optional[str]) – On services that support it, a token to get a fresh access token.
  • +
  • account_id (str) - Identifier of the OAuth account on the corresponding service.
  • +
  • account_email (str) - Email address of the OAuth account on the corresponding service.
  • +
+

Setup the database adapter

+

SQLAlchemy

+

You'll need to define the table for storing the OAuth account model. We provide a base one for this:

+
from fastapi_users.db.sqlalchemy import SQLAlchemyBaseOAuthAccountTable
+
+class OAuthAccount(SQLAlchemyBaseOAuthAccountTable, Base):
+    pass
+
+ +

Then, you should declare it on the database adapter:

+
user_db = SQLAlchemyUserDatabase(UserDB, database, User.__table__, OAuthAccount.__table__)
+
+ +

MongoDB

+

Nothing to do, the basic configuration is enough.

+

Tortoise ORM

+

You'll need to define the Tortoise model for storing the OAuth account model. We provide a base one for this:

+
from fastapi_users.db.tortoise import TortoiseBaseOAuthAccountModel
+
+
+class OAuthAccount(TortoiseBaseOAuthAccountModel):
+    user = fields.ForeignKeyField("models.User", related_name="oauth_accounts")
+
+ +
+

Warning

+

Note that you shouls define the foreign key yourself, so that you can point it the user model in your namespace.

+
+

Then, you should declare it on the database adapter:

+
user_db = TortoiseUserDatabase(UserDB, User, OAuthAccount)
+
+ +

Generate a router

+

Once you have a FastAPIUsers instance, you can make it generate a single OAuth router for the given client.

+
from fastapi import FastAPI
+from fastapi_users import FastAPIUsers
+from httpx_oauth.clients.google import GoogleOAuth2
+
+google_oauth_client = GoogleOAuth2("CLIENT_ID", "CLIENT_SECRET")
+
+app = FastAPI()
+fastapi_users = FastAPIUsers(
+    user_db, auth_backends, User, UserCreate, UserUpdate, UserDB, SECRET,
+)
+
+google_oauth_router = fastapi_users.get_oauth_router(google_oauth_client, SECRET)
+
+app.include_router(google_oauth_router, prefix="/google-oauth", tags=["users"])
+
+ +

Full example

+
+ + +
import databases
+import sqlalchemy
+from fastapi import FastAPI
+from fastapi_users import FastAPIUsers, models
+from fastapi_users.authentication import JWTAuthentication
+from fastapi_users.db import (
+    SQLAlchemyBaseOAuthAccountTable,
+    SQLAlchemyBaseUserTable,
+    SQLAlchemyUserDatabase,
+)
+from httpx_oauth.clients.google import GoogleOAuth2
+from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base
+
+DATABASE_URL = "sqlite:///./test.db"
+SECRET = "SECRET"
+
+
+google_oauth_client = GoogleOAuth2("CLIENT_ID", "CLIENT_SECRET")
+
+
+class User(models.BaseUser, models.BaseOAuthAccountMixin):
+    pass
+
+
+class UserCreate(User, models.BaseUserCreate):
+    pass
+
+
+class UserUpdate(User, models.BaseUserUpdate):
+    pass
+
+
+class UserDB(User, models.BaseUserDB):
+    pass
+
+
+database = databases.Database(DATABASE_URL)
+Base: DeclarativeMeta = declarative_base()
+
+
+class UserTable(Base, SQLAlchemyBaseUserTable):
+    pass
+
+
+class OAuthAccount(SQLAlchemyBaseOAuthAccountTable, Base):
+    pass
+
+
+engine = sqlalchemy.create_engine(
+    DATABASE_URL, connect_args={"check_same_thread": False}
+)
+Base.metadata.create_all(engine)
+
+users = UserTable.__table__
+oauth_accounts = OAuthAccount.__table__
+user_db = SQLAlchemyUserDatabase(UserDB, database, users, oauth_accounts)
+
+
+auth_backends = [
+    JWTAuthentication(secret=SECRET, lifetime_seconds=3600),
+]
+
+app = FastAPI()
+fastapi_users = FastAPIUsers(
+    user_db, auth_backends, User, UserCreate, UserUpdate, UserDB, SECRET,
+)
+app.include_router(fastapi_users.router, prefix="/users", tags=["users"])
+
+google_oauth_router = fastapi_users.get_oauth_router(google_oauth_client, SECRET)
+app.include_router(google_oauth_router, prefix="/google-oauth", tags=["users"])
+
+
+@fastapi_users.on_after_register()
+def on_after_register(user: User):
+    print(f"User {user.id} has registered.")
+
+
+@fastapi_users.on_after_forgot_password()
+def on_after_forgot_password(user: User, token: str):
+    print(f"User {user.id} has forgot their password. Reset token: {token}")
+
+
+@app.on_event("startup")
+async def startup():
+    await database.connect()
+
+
+@app.on_event("shutdown")
+async def shutdown():
+    await database.disconnect()
+
+ + +
import motor.motor_asyncio
+from fastapi import FastAPI
+from fastapi_users import FastAPIUsers, models
+from fastapi_users.authentication import JWTAuthentication
+from fastapi_users.db import MongoDBUserDatabase
+from httpx_oauth.clients.google import GoogleOAuth2
+
+DATABASE_URL = "mongodb://localhost:27017"
+SECRET = "SECRET"
+
+
+google_oauth_client = GoogleOAuth2("CLIENT_ID", "CLIENT_SECRET")
+
+
+class User(models.BaseUser, models.BaseOAuthAccountMixin):
+    pass
+
+
+class UserCreate(User, models.BaseUserCreate):
+    pass
+
+
+class UserUpdate(User, models.BaseUserUpdate):
+    pass
+
+
+class UserDB(User, models.BaseUserDB):
+    pass
+
+
+client = motor.motor_asyncio.AsyncIOMotorClient(DATABASE_URL)
+db = client["database_name"]
+collection = db["users"]
+user_db = MongoDBUserDatabase(UserDB, collection)
+
+auth_backends = [
+    JWTAuthentication(secret=SECRET, lifetime_seconds=3600),
+]
+
+app = FastAPI()
+fastapi_users = FastAPIUsers(
+    user_db, auth_backends, User, UserCreate, UserUpdate, UserDB, SECRET,
+)
+app.include_router(fastapi_users.router, prefix="/users", tags=["users"])
+
+google_oauth_router = fastapi_users.get_oauth_router(google_oauth_client, SECRET)
+app.include_router(google_oauth_router, prefix="/google-oauth", tags=["users"])
+
+
+@fastapi_users.on_after_register()
+def on_after_register(user: User):
+    print(f"User {user.id} has registered.")
+
+
+@fastapi_users.on_after_forgot_password()
+def on_after_forgot_password(user: User, token: str):
+    print(f"User {user.id} has forgot their password. Reset token: {token}")
+
+ + +
from fastapi import FastAPI
+from fastapi_users import FastAPIUsers, models
+from fastapi_users.authentication import JWTAuthentication
+from fastapi_users.db import (
+    TortoiseBaseOAuthAccountModel,
+    TortoiseBaseUserModel,
+    TortoiseUserDatabase,
+)
+from httpx_oauth.clients.google import GoogleOAuth2
+from tortoise import fields
+from tortoise.contrib.starlette import register_tortoise
+
+DATABASE_URL = "sqlite://./test.db"
+SECRET = "SECRET"
+
+
+google_oauth_client = GoogleOAuth2("CLIENT_ID", "CLIENT_SECRET")
+
+
+class User(models.BaseUser, models.BaseOAuthAccountMixin):
+    pass
+
+
+class UserCreate(User, models.BaseUserCreate):
+    pass
+
+
+class UserUpdate(User, models.BaseUserUpdate):
+    pass
+
+
+class UserDB(User, models.BaseUserDB):
+    pass
+
+
+class UserModel(TortoiseBaseUserModel):
+    pass
+
+
+class OAuthAccountModel(TortoiseBaseOAuthAccountModel):
+    user = fields.ForeignKeyField("models.UserModel", related_name="oauth_accounts")
+
+
+user_db = TortoiseUserDatabase(UserDB, UserModel, OAuthAccountModel)
+app = FastAPI()
+register_tortoise(app, db_url=DATABASE_URL, modules={"models": ["test"]})
+
+auth_backends = [
+    JWTAuthentication(secret=SECRET, lifetime_seconds=3600),
+]
+
+fastapi_users = FastAPIUsers(
+    user_db, auth_backends, User, UserCreate, UserUpdate, UserDB, SECRET,
+)
+app.include_router(fastapi_users.router, prefix="/users", tags=["users"])
+
+google_oauth_router = fastapi_users.get_oauth_router(google_oauth_client, SECRET)
+app.include_router(google_oauth_router, prefix="/google-oauth", tags=["users"])
+
+
+@fastapi_users.on_after_register()
+def on_after_register(user: User):
+    print(f"User {user.id} has registered.")
+
+
+@fastapi_users.on_after_forgot_password()
+def on_after_forgot_password(user: User, token: str):
+    print(f"User {user.id} has forgot their password. Reset token: {token}")
+
+
+ + + + + + + + + + +
+
+
+