diff --git a/dev/404.html b/dev/404.html index 05f6c7c4..26ab4211 100644 --- a/dev/404.html +++ b/dev/404.html @@ -14,7 +14,7 @@ - + diff --git a/dev/configuration/authentication/backend/index.html b/dev/configuration/authentication/backend/index.html index c4648d0b..ceecf56b 100644 --- a/dev/configuration/authentication/backend/index.html +++ b/dev/configuration/authentication/backend/index.html @@ -18,7 +18,7 @@ - + diff --git a/dev/configuration/authentication/index.html b/dev/configuration/authentication/index.html index 6a15423a..1c1fb6b6 100644 --- a/dev/configuration/authentication/index.html +++ b/dev/configuration/authentication/index.html @@ -18,7 +18,7 @@ - + diff --git a/dev/configuration/authentication/strategies/database/index.html b/dev/configuration/authentication/strategies/database/index.html index ec4b3b0c..650cb0f7 100644 --- a/dev/configuration/authentication/strategies/database/index.html +++ b/dev/configuration/authentication/strategies/database/index.html @@ -18,7 +18,7 @@ - + @@ -1664,7 +1664,7 @@

We are providing a base model with those fields for each database we are supporting.

SQLAlchemy

We'll expand from the basic SQLAlchemy configuration.

-
from typing import AsyncGenerator
+
from collections.abc import AsyncGenerator
 
 from fastapi import Depends
 from fastapi_users.db import SQLAlchemyBaseUserTableUUID, SQLAlchemyUserDatabase
diff --git a/dev/configuration/authentication/strategies/jwt/index.html b/dev/configuration/authentication/strategies/jwt/index.html
index ad20fb6a..c9003609 100644
--- a/dev/configuration/authentication/strategies/jwt/index.html
+++ b/dev/configuration/authentication/strategies/jwt/index.html
@@ -18,7 +18,7 @@
       
       
       
-      
+      
     
     
       
diff --git a/dev/configuration/authentication/strategies/redis/index.html b/dev/configuration/authentication/strategies/redis/index.html
index 9f40a6b3..6903a3df 100644
--- a/dev/configuration/authentication/strategies/redis/index.html
+++ b/dev/configuration/authentication/strategies/redis/index.html
@@ -18,7 +18,7 @@
       
       
       
-      
+      
     
     
       
diff --git a/dev/configuration/authentication/transports/bearer/index.html b/dev/configuration/authentication/transports/bearer/index.html
index 0d53a0a8..a414fb4d 100644
--- a/dev/configuration/authentication/transports/bearer/index.html
+++ b/dev/configuration/authentication/transports/bearer/index.html
@@ -18,7 +18,7 @@
       
       
       
-      
+      
     
     
       
diff --git a/dev/configuration/authentication/transports/cookie/index.html b/dev/configuration/authentication/transports/cookie/index.html
index b2437209..d6fbf044 100644
--- a/dev/configuration/authentication/transports/cookie/index.html
+++ b/dev/configuration/authentication/transports/cookie/index.html
@@ -18,7 +18,7 @@
       
       
       
-      
+      
     
     
       
diff --git a/dev/configuration/databases/beanie/index.html b/dev/configuration/databases/beanie/index.html
index 0a15939c..1343a545 100644
--- a/dev/configuration/databases/beanie/index.html
+++ b/dev/configuration/databases/beanie/index.html
@@ -18,7 +18,7 @@
       
       
       
-      
+      
     
     
       
diff --git a/dev/configuration/databases/sqlalchemy/index.html b/dev/configuration/databases/sqlalchemy/index.html
index ccec287b..d61d0ae8 100644
--- a/dev/configuration/databases/sqlalchemy/index.html
+++ b/dev/configuration/databases/sqlalchemy/index.html
@@ -18,7 +18,7 @@
       
       
       
-      
+      
     
     
       
@@ -1608,7 +1608,7 @@
 

Create the User model

As for any SQLAlchemy ORM model, we'll create a User model.

-
from typing import AsyncGenerator
+
from collections.abc import AsyncGenerator
 
 from fastapi import Depends
 from fastapi_users.db import SQLAlchemyBaseUserTableUUID, SQLAlchemyUserDatabase
@@ -1654,7 +1654,7 @@
 

Implement a function to create the tables

We'll now create an utility function to create all the defined tables.

-
from typing import AsyncGenerator
+
from collections.abc import AsyncGenerator
 
 from fastapi import Depends
 from fastapi_users.db import SQLAlchemyBaseUserTableUUID, SQLAlchemyUserDatabase
@@ -1696,7 +1696,7 @@
 

Create the database adapter dependency

The database adapter of FastAPI Users makes the link between your database configuration and the users logic. It should be generated by a FastAPI dependency.

-
from typing import AsyncGenerator
+
from collections.abc import AsyncGenerator
 
 from fastapi import Depends
 from fastapi_users.db import SQLAlchemyBaseUserTableUUID, SQLAlchemyUserDatabase
diff --git a/dev/configuration/full-example/index.html b/dev/configuration/full-example/index.html
index c06c9dc9..19e10f19 100644
--- a/dev/configuration/full-example/index.html
+++ b/dev/configuration/full-example/index.html
@@ -18,7 +18,7 @@
       
       
       
-      
+      
     
     
       
@@ -1643,7 +1643,7 @@ Insecure passwords may give attackers full access to your database.

-
from typing import AsyncGenerator
+
from collections.abc import AsyncGenerator
 
 from fastapi import Depends
 from fastapi_users.db import SQLAlchemyBaseUserTableUUID, SQLAlchemyUserDatabase
diff --git a/dev/configuration/oauth/index.html b/dev/configuration/oauth/index.html
index 25235da5..ca83fc74 100644
--- a/dev/configuration/oauth/index.html
+++ b/dev/configuration/oauth/index.html
@@ -18,7 +18,7 @@
       
       
       
-      
+      
     
     
       
@@ -1814,7 +1814,7 @@
 

Setup the database adapter

SQLAlchemy

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

-
from typing import AsyncGenerator, List
+
from collections.abc import AsyncGenerator
 
 from fastapi import Depends
 from fastapi_users.db import (
@@ -1837,7 +1837,7 @@
 
 
 class User(SQLAlchemyBaseUserTableUUID, Base):
-    oauth_accounts: Mapped[List[OAuthAccount]] = relationship(
+    oauth_accounts: Mapped[list[OAuthAccount]] = relationship(
         "OAuthAccount", lazy="joined"
     )
 
@@ -1875,30 +1875,28 @@
 

Beanie

The advantage of MongoDB is that you can easily embed sub-objects in a single document. That's why the configuration for Beanie is quite simple. All we need to do is to define another class to structure an OAuth account object.

-
from typing import List
-
-import motor.motor_asyncio
-from beanie import Document
-from fastapi_users.db import BaseOAuthAccount, BeanieBaseUser, BeanieUserDatabase
-from pydantic import Field
-
-DATABASE_URL = "mongodb://localhost:27017"
-client = motor.motor_asyncio.AsyncIOMotorClient(
-    DATABASE_URL, uuidRepresentation="standard"
-)
-db = client["database_name"]
-
-
-class OAuthAccount(BaseOAuthAccount):
-    pass
-
-
-class User(BeanieBaseUser, Document):
-    oauth_accounts: List[OAuthAccount] = Field(default_factory=list)
-
-
-async def get_user_db():
-    yield BeanieUserDatabase(User, OAuthAccount)
+
import motor.motor_asyncio
+from beanie import Document
+from fastapi_users.db import BaseOAuthAccount, BeanieBaseUser, BeanieUserDatabase
+from pydantic import Field
+
+DATABASE_URL = "mongodb://localhost:27017"
+client = motor.motor_asyncio.AsyncIOMotorClient(
+    DATABASE_URL, uuidRepresentation="standard"
+)
+db = client["database_name"]
+
+
+class OAuthAccount(BaseOAuthAccount):
+    pass
+
+
+class User(BeanieBaseUser, Document):
+    oauth_accounts: list[OAuthAccount] = Field(default_factory=list)
+
+
+async def get_user_db():
+    yield BeanieUserDatabase(User, OAuthAccount)
 

It's worth to note that OAuthAccount is not a Beanie document but a Pydantic model that we'll embed inside the User document, through the oauth_accounts array.

Generate routers

@@ -2051,7 +2049,7 @@ Insecure passwords may give attackers full access to your database.

-
from typing import AsyncGenerator, List
+
from collections.abc import AsyncGenerator
 
 from fastapi import Depends
 from fastapi_users.db import (
@@ -2074,7 +2072,7 @@ Insecure passwords may give attackers full access to your database.

class User(SQLAlchemyBaseUserTableUUID, Base): - oauth_accounts: Mapped[List[OAuthAccount]] = relationship( + oauth_accounts: Mapped[list[OAuthAccount]] = relationship( "OAuthAccount", lazy="joined" ) @@ -2265,30 +2263,28 @@ Insecure passwords may give attackers full access to your database.

-
from typing import List
-
-import motor.motor_asyncio
-from beanie import Document
-from fastapi_users.db import BaseOAuthAccount, BeanieBaseUser, BeanieUserDatabase
-from pydantic import Field
-
-DATABASE_URL = "mongodb://localhost:27017"
-client = motor.motor_asyncio.AsyncIOMotorClient(
-    DATABASE_URL, uuidRepresentation="standard"
-)
-db = client["database_name"]
-
-
-class OAuthAccount(BaseOAuthAccount):
-    pass
-
-
-class User(BeanieBaseUser, Document):
-    oauth_accounts: List[OAuthAccount] = Field(default_factory=list)
-
-
-async def get_user_db():
-    yield BeanieUserDatabase(User, OAuthAccount)
+
import motor.motor_asyncio
+from beanie import Document
+from fastapi_users.db import BaseOAuthAccount, BeanieBaseUser, BeanieUserDatabase
+from pydantic import Field
+
+DATABASE_URL = "mongodb://localhost:27017"
+client = motor.motor_asyncio.AsyncIOMotorClient(
+    DATABASE_URL, uuidRepresentation="standard"
+)
+db = client["database_name"]
+
+
+class OAuthAccount(BaseOAuthAccount):
+    pass
+
+
+class User(BeanieBaseUser, Document):
+    oauth_accounts: list[OAuthAccount] = Field(default_factory=list)
+
+
+async def get_user_db():
+    yield BeanieUserDatabase(User, OAuthAccount)
 
diff --git a/dev/configuration/overview/index.html b/dev/configuration/overview/index.html index 1ad9c7ce..2a26f11e 100644 --- a/dev/configuration/overview/index.html +++ b/dev/configuration/overview/index.html @@ -18,7 +18,7 @@ - + diff --git a/dev/configuration/password-hash/index.html b/dev/configuration/password-hash/index.html index c1cd9b18..14fa861b 100644 --- a/dev/configuration/password-hash/index.html +++ b/dev/configuration/password-hash/index.html @@ -18,7 +18,7 @@ - + diff --git a/dev/configuration/routers/auth/index.html b/dev/configuration/routers/auth/index.html index 669471b3..90212d80 100644 --- a/dev/configuration/routers/auth/index.html +++ b/dev/configuration/routers/auth/index.html @@ -18,7 +18,7 @@ - + diff --git a/dev/configuration/routers/index.html b/dev/configuration/routers/index.html index 11b37c2a..e6a80ecb 100644 --- a/dev/configuration/routers/index.html +++ b/dev/configuration/routers/index.html @@ -18,7 +18,7 @@ - + diff --git a/dev/configuration/routers/register/index.html b/dev/configuration/routers/register/index.html index ab76e87e..308821ef 100644 --- a/dev/configuration/routers/register/index.html +++ b/dev/configuration/routers/register/index.html @@ -18,7 +18,7 @@ - + diff --git a/dev/configuration/routers/reset/index.html b/dev/configuration/routers/reset/index.html index 2ee00b29..bdc12067 100644 --- a/dev/configuration/routers/reset/index.html +++ b/dev/configuration/routers/reset/index.html @@ -18,7 +18,7 @@ - + diff --git a/dev/configuration/routers/users/index.html b/dev/configuration/routers/users/index.html index 7a753870..6fa284cd 100644 --- a/dev/configuration/routers/users/index.html +++ b/dev/configuration/routers/users/index.html @@ -18,7 +18,7 @@ - + diff --git a/dev/configuration/routers/verify/index.html b/dev/configuration/routers/verify/index.html index c199a199..c3a2f6cc 100644 --- a/dev/configuration/routers/verify/index.html +++ b/dev/configuration/routers/verify/index.html @@ -18,7 +18,7 @@ - + diff --git a/dev/configuration/schemas/index.html b/dev/configuration/schemas/index.html index 675511d8..3938b834 100644 --- a/dev/configuration/schemas/index.html +++ b/dev/configuration/schemas/index.html @@ -18,7 +18,7 @@ - + diff --git a/dev/configuration/user-manager/index.html b/dev/configuration/user-manager/index.html index db2649b1..1cb3fee7 100644 --- a/dev/configuration/user-manager/index.html +++ b/dev/configuration/user-manager/index.html @@ -18,7 +18,7 @@ - + diff --git a/dev/cookbook/create-user-programmatically/index.html b/dev/cookbook/create-user-programmatically/index.html index 3d10c5c7..34b1e455 100644 --- a/dev/cookbook/create-user-programmatically/index.html +++ b/dev/cookbook/create-user-programmatically/index.html @@ -18,7 +18,7 @@ - + diff --git a/dev/index.html b/dev/index.html index f6d4ea59..ed5b53f7 100644 --- a/dev/index.html +++ b/dev/index.html @@ -16,7 +16,7 @@ - + diff --git a/dev/installation/index.html b/dev/installation/index.html index a05f4fad..3c83f3ee 100644 --- a/dev/installation/index.html +++ b/dev/installation/index.html @@ -18,7 +18,7 @@ - + diff --git a/dev/migration/08_to_1x/index.html b/dev/migration/08_to_1x/index.html index e9096cb6..c533da44 100644 --- a/dev/migration/08_to_1x/index.html +++ b/dev/migration/08_to_1x/index.html @@ -18,7 +18,7 @@ - + diff --git a/dev/migration/1x_to_2x/index.html b/dev/migration/1x_to_2x/index.html index 654dd9d7..97375c50 100644 --- a/dev/migration/1x_to_2x/index.html +++ b/dev/migration/1x_to_2x/index.html @@ -18,7 +18,7 @@ - + diff --git a/dev/migration/2x_to_3x/index.html b/dev/migration/2x_to_3x/index.html index c82f43aa..2a5043ab 100644 --- a/dev/migration/2x_to_3x/index.html +++ b/dev/migration/2x_to_3x/index.html @@ -18,7 +18,7 @@ - + diff --git a/dev/migration/3x_to_4x/index.html b/dev/migration/3x_to_4x/index.html index 85b19147..3d13930e 100644 --- a/dev/migration/3x_to_4x/index.html +++ b/dev/migration/3x_to_4x/index.html @@ -18,7 +18,7 @@ - + diff --git a/dev/migration/4x_to_5x/index.html b/dev/migration/4x_to_5x/index.html index 29b65cbe..ffeeaa66 100644 --- a/dev/migration/4x_to_5x/index.html +++ b/dev/migration/4x_to_5x/index.html @@ -18,7 +18,7 @@ - + diff --git a/dev/migration/6x_to_7x/index.html b/dev/migration/6x_to_7x/index.html index 4e4ae067..a077e286 100644 --- a/dev/migration/6x_to_7x/index.html +++ b/dev/migration/6x_to_7x/index.html @@ -18,7 +18,7 @@ - + diff --git a/dev/migration/7x_to_8x/index.html b/dev/migration/7x_to_8x/index.html index 6f80d96c..27d16bce 100644 --- a/dev/migration/7x_to_8x/index.html +++ b/dev/migration/7x_to_8x/index.html @@ -18,7 +18,7 @@ - + diff --git a/dev/migration/8x_to_9x/index.html b/dev/migration/8x_to_9x/index.html index 765c6d92..f52caf69 100644 --- a/dev/migration/8x_to_9x/index.html +++ b/dev/migration/8x_to_9x/index.html @@ -18,7 +18,7 @@ - + diff --git a/dev/migration/9x_to_10x/index.html b/dev/migration/9x_to_10x/index.html index 74fb9925..ffbcf42d 100644 --- a/dev/migration/9x_to_10x/index.html +++ b/dev/migration/9x_to_10x/index.html @@ -16,7 +16,7 @@ - + diff --git a/dev/search/search_index.json b/dev/search/search_index.json index 3595e9d3..68e3c993 100644 --- a/dev/search/search_index.json +++ b/dev/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"FastAPI Users","text":"

Ready-to-use and customizable users management for FastAPI

Documentation: https://fastapi-users.github.io/fastapi-users/

Source Code: https://github.com/fastapi-users/fastapi-users

Add quickly a registration and authentication system to your FastAPI project. FastAPI Users is designed to be as customizable and adaptable as possible.

"},{"location":"#features","title":"Features","text":"
  • Extensible base user model
  • Ready-to-use register, login, reset password and verify e-mail routes
  • Ready-to-use social OAuth2 login flow
  • Dependency callables to inject current user in route
  • Pluggable password validation
  • Customizable database backend
    • SQLAlchemy ORM async included
    • MongoDB with Beanie ODM included
  • Multiple customizable authentication backends
    • Transports: Authorization header, Cookie
    • Strategies: JWT, Database, Redis
  • Full OpenAPI schema support, even with several authentication backends
"},{"location":"#in-a-hurry-discover-fief-the-open-source-authentication-platform","title":"In a hurry? Discover Fief, the open-source authentication platform","text":"

Implementing registration, login, social auth is hard and painful. We know it. With our highly secure and open-source users management platform, you can focus on your app while staying in control of your users data.

  • Open-source: self-host it for free
  • Pre-built login and registration pages: clean and fast authentication so you don't have to do it yourself
  • Official Python client with built-in FastAPI integration

It's free and open-source

"},{"location":"#contributors-and-sponsors","title":"Contributors and sponsors \u2728\u2615\ufe0f","text":"

Thanks goes to these wonderful people (emoji key):

Fran\u00e7ois Voron\ud83d\udea7 Paolo Dina\ud83d\udcb5 \ud83d\udcbb Dmytro Ohorodnik\ud83d\udc1b Matthew D. Scholefield\ud83d\udc1b \ud83d\udcbb roywes\ud83d\udc1b \ud83d\udcbb Satwik Kansal\ud83d\udcd6 Edd Salkield\ud83d\udcbb \ud83d\udcd6 mark-todd\ud83d\udcbb \ud83d\udcd6 lill74\ud83d\udc1b \ud83d\udcbb \ud83d\udcd6 SelfhostedPro\ud83d\udee1\ufe0f \ud83d\udcbb Oskar Gmerek\ud83d\udcd6 Martin Collado\ud83d\udc1b \ud83d\udcbb Eric Lopes\ud83d\udcd6 \ud83d\udee1\ufe0f Beau Breon\ud83d\udcbb Niyas Mohammed\ud83d\udcd6 prostomarkeloff\ud83d\udcd6 \ud83d\udcbb Marius M\u00e9zerette\ud83d\udc1b \ud83e\udd14 Nickolas Grigoriadis\ud83d\udc1b Open Data Coder\ud83e\udd14 Mohammed Alshehri\ud83e\udd14 Tyler Renelle\ud83e\udd14 collerek\ud83d\udcbb Robert Bracco\ud83d\udcb5 Augusto Herrmann\ud83d\udcd6 Smithybrewer\ud83d\udc1b silllli\ud83d\udcd6 alexferrari88\ud83d\udcb5 sandalwoodbox\ud83d\udc1b \ud83d\udcd6 Vlad Hoi\ud83d\udcd6 Joe Nudell\ud83d\udc1b Ben\ud83d\udcbb BoYanZh\ud83d\udcd6 David Brochart\ud83d\udcd6 \ud83d\udcbb Daan Beverdam\ud83d\udcbb St\u00e9phane Raimbault\u26a0\ufe0f \ud83d\udc1b Sondre Lilleb\u00f8 Gundersen\ud83d\udcd6 Maxim\ud83d\udcd6 \ud83d\udc1b scottdavort\ud83d\udcb5 John Dukewich\ud83d\udcd6 Yasser Tahiri\ud83d\udcbb Brandon H. Goding\ud83d\udcbb \ud83d\udcd6 PovilasK\ud83d\udcbb Just van den Broecke\ud83d\udcb5 jakemanger\ud83d\udc1b \ud83d\udcbb Ikko Ashimine\ud83d\udcbb Maty\u00e1\u0161 Richter\ud83d\udcbb Hazedd\ud83d\udc1b \ud83d\udcd6 Luis Roel\ud83d\udcb5 Alexandr Makurin\ud83d\udcbb \ud83d\udc1b Leon Thurner\ud83d\udcd6 Goran Meki\u0107\ud83d\udce6 Gaganpreet\ud83d\udcbb Joe Taylor\ud83d\udcbb Richard Friberg\ud83d\udc1b Kenton Parton\ud83d\udcb5 Adrian Cio\u0142ek\ud83d\udc1b \u2b55Alexander Rymdeko-Harvey\ud83d\udcd6 schwannden\ud83d\udea7 \ud83d\udcbb Jimmy Angel P\u00e9rez D\u00edaz\ud83d\udee1\ufe0f Austin Orr\ud83d\udea7 Carlo Eugster\ud83d\udee1\ufe0f Vittorio Zamboni\ud83d\udcbb Andrey\ud83d\udcd6 Can H. Tartanoglu\ud83d\udc1b Filipe Nascimento\ud83d\udee1\ufe0f dudulu\ud83d\udcb5 \ud83d\udc1b \ud83d\udcac Toni Alatalo\ud83d\udcbb \ud83d\udcd6 B\u00f6rge Kiss\ud83d\udcd6 Guilherme Caminha\ud83d\udcd6 T\u00e9va KRIEF\ud83d\udcbb Essa Alshammri\ud83d\udcd6 0xJan\ud83d\udc1b Justin Thomas\ud83d\udcbb Adam Israel\ud83d\udcbb Nerixjk\ud83d\udc1b \ud83d\udcbb Mike Fotinakis\ud83d\udcbb \ud83d\udc1b lifengmds\ud83d\udcb5 raindata5\ud83d\udcd6 Mark Donnelly\ud83d\udcd6 Alexander Zinov\ud83d\udcbb

This project follows the all-contributors specification. Contributions of any kind welcome!

"},{"location":"#development","title":"Development","text":""},{"location":"#setup-environment","title":"Setup environment","text":"

We use Hatch to manage the development environment and production build. Ensure it's installed on your system.

"},{"location":"#run-unit-tests","title":"Run unit tests","text":"

You can run all the tests with:

hatch run test:test\n
"},{"location":"#format-the-code","title":"Format the code","text":"

Execute the following command to apply linting and check typing:

hatch run lint\n
"},{"location":"#serve-the-documentation","title":"Serve the documentation","text":"

You can serve the documentation locally with the following command:

hatch run docs\n

The documentation will be available on http://localhost:8000.

"},{"location":"#license","title":"License","text":"

This project is licensed under the terms of the MIT license.

"},{"location":"installation/","title":"Installation","text":"

You can add FastAPI Users to your FastAPI project in a few easy steps. First of all, install the dependency:

"},{"location":"installation/#with-sqlalchemy-support","title":"With SQLAlchemy support","text":"
pip install 'fastapi-users[sqlalchemy]'\n
"},{"location":"installation/#with-beanie-support","title":"With Beanie support","text":"
pip install 'fastapi-users[beanie]'\n
"},{"location":"installation/#with-redis-authentication-backend-support","title":"With Redis authentication backend support","text":"

Information on installing with proper database support can be found in the Redis section.

"},{"location":"installation/#with-oauth2-support","title":"With OAuth2 support","text":"

Information on installing with proper database support can be found in the OAuth2 section.

That's it! In the next section, we'll have an overview of how things work.

"},{"location":"configuration/full-example/","title":"Full example","text":"

Here is a full working example with JWT authentication to help get you started.

Warning

Notice that SECRET should be changed to a strong passphrase. Insecure passwords may give attackers full access to your database.

"},{"location":"configuration/full-example/#sqlalchemy","title":"SQLAlchemy","text":"

Open

requirements.txtmain.pyapp/app.pyapp/db.pyapp/schemas.pyapp/users.py
fastapi\nfastapi-users[sqlalchemy]\nuvicorn[standard]\naiosqlite\n
import uvicorn\n\nif __name__ == \"__main__\":\n    uvicorn.run(\"app.app:app\", host=\"0.0.0.0\", log_level=\"info\")\n
from contextlib import asynccontextmanager\n\nfrom fastapi import Depends, FastAPI\n\nfrom app.db import User, create_db_and_tables\nfrom app.schemas import UserCreate, UserRead, UserUpdate\nfrom app.users import auth_backend, current_active_user, fastapi_users\n\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    # Not needed if you setup a migration system like Alembic\n    await create_db_and_tables()\n    yield\n\n\napp = FastAPI(lifespan=lifespan)\n\napp.include_router(\n    fastapi_users.get_auth_router(auth_backend), prefix=\"/auth/jwt\", tags=[\"auth\"]\n)\napp.include_router(\n    fastapi_users.get_register_router(UserRead, UserCreate),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\napp.include_router(\n    fastapi_users.get_reset_password_router(),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\napp.include_router(\n    fastapi_users.get_verify_router(UserRead),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\napp.include_router(\n    fastapi_users.get_users_router(UserRead, UserUpdate),\n    prefix=\"/users\",\n    tags=[\"users\"],\n)\n\n\n@app.get(\"/authenticated-route\")\nasync def authenticated_route(user: User = Depends(current_active_user)):\n    return {\"message\": f\"Hello {user.email}!\"}\n
from typing import AsyncGenerator\n\nfrom fastapi import Depends\nfrom fastapi_users.db import SQLAlchemyBaseUserTableUUID, SQLAlchemyUserDatabase\nfrom sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine\nfrom sqlalchemy.orm import DeclarativeBase\n\nDATABASE_URL = \"sqlite+aiosqlite:///./test.db\"\n\n\nclass Base(DeclarativeBase):\n    pass\n\n\nclass User(SQLAlchemyBaseUserTableUUID, Base):\n    pass\n\n\nengine = create_async_engine(DATABASE_URL)\nasync_session_maker = async_sessionmaker(engine, expire_on_commit=False)\n\n\nasync def create_db_and_tables():\n    async with engine.begin() as conn:\n        await conn.run_sync(Base.metadata.create_all)\n\n\nasync def get_async_session() -> AsyncGenerator[AsyncSession, None]:\n    async with async_session_maker() as session:\n        yield session\n\n\nasync def get_user_db(session: AsyncSession = Depends(get_async_session)):\n    yield SQLAlchemyUserDatabase(session, User)\n
import uuid\n\nfrom fastapi_users import schemas\n\n\nclass UserRead(schemas.BaseUser[uuid.UUID]):\n    pass\n\n\nclass UserCreate(schemas.BaseUserCreate):\n    pass\n\n\nclass UserUpdate(schemas.BaseUserUpdate):\n    pass\n
import uuid\nfrom typing import Optional\n\nfrom fastapi import Depends, Request\nfrom fastapi_users import BaseUserManager, FastAPIUsers, UUIDIDMixin, models\nfrom fastapi_users.authentication import (\n    AuthenticationBackend,\n    BearerTransport,\n    JWTStrategy,\n)\nfrom fastapi_users.db import SQLAlchemyUserDatabase\n\nfrom app.db import User, get_user_db\n\nSECRET = \"SECRET\"\n\n\nclass UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):\n    reset_password_token_secret = SECRET\n    verification_token_secret = SECRET\n\n    async def on_after_register(self, user: User, request: Optional[Request] = None):\n        print(f\"User {user.id} has registered.\")\n\n    async def on_after_forgot_password(\n        self, user: User, token: str, request: Optional[Request] = None\n    ):\n        print(f\"User {user.id} has forgot their password. Reset token: {token}\")\n\n    async def on_after_request_verify(\n        self, user: User, token: str, request: Optional[Request] = None\n    ):\n        print(f\"Verification requested for user {user.id}. Verification token: {token}\")\n\n\nasync def get_user_manager(user_db: SQLAlchemyUserDatabase = Depends(get_user_db)):\n    yield UserManager(user_db)\n\n\nbearer_transport = BearerTransport(tokenUrl=\"auth/jwt/login\")\n\n\ndef get_jwt_strategy() -> JWTStrategy[models.UP, models.ID]:\n    return JWTStrategy(secret=SECRET, lifetime_seconds=3600)\n\n\nauth_backend = AuthenticationBackend(\n    name=\"jwt\",\n    transport=bearer_transport,\n    get_strategy=get_jwt_strategy,\n)\n\nfastapi_users = FastAPIUsers[User, uuid.UUID](get_user_manager, [auth_backend])\n\ncurrent_active_user = fastapi_users.current_user(active=True)\n
"},{"location":"configuration/full-example/#beanie","title":"Beanie","text":"

Open

requirements.txtmain.pyapp/app.pyapp/db.pyapp/schemas.pyapp/users.py
fastapi\nfastapi-users[beanie]\nuvicorn[standard]\n
import uvicorn\n\nif __name__ == \"__main__\":\n    uvicorn.run(\"app.app:app\", host=\"0.0.0.0\", log_level=\"info\")\n
from contextlib import asynccontextmanager\n\nfrom beanie import init_beanie\nfrom fastapi import Depends, FastAPI\n\nfrom app.db import User, db\nfrom app.schemas import UserCreate, UserRead, UserUpdate\nfrom app.users import auth_backend, current_active_user, fastapi_users\n\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    await init_beanie(\n        database=db,\n        document_models=[\n            User,\n        ],\n    )\n    yield\n\n\napp = FastAPI(lifespan=lifespan)\n\n\napp.include_router(\n    fastapi_users.get_auth_router(auth_backend), prefix=\"/auth/jwt\", tags=[\"auth\"]\n)\napp.include_router(\n    fastapi_users.get_register_router(UserRead, UserCreate),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\napp.include_router(\n    fastapi_users.get_reset_password_router(),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\napp.include_router(\n    fastapi_users.get_verify_router(UserRead),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\napp.include_router(\n    fastapi_users.get_users_router(UserRead, UserUpdate),\n    prefix=\"/users\",\n    tags=[\"users\"],\n)\n\n\n@app.get(\"/authenticated-route\")\nasync def authenticated_route(user: User = Depends(current_active_user)):\n    return {\"message\": f\"Hello {user.email}!\"}\n
import motor.motor_asyncio\nfrom beanie import Document\nfrom fastapi_users.db import BeanieBaseUser\nfrom fastapi_users_db_beanie import BeanieUserDatabase\n\nDATABASE_URL = \"mongodb://localhost:27017\"\nclient = motor.motor_asyncio.AsyncIOMotorClient(\n    DATABASE_URL, uuidRepresentation=\"standard\"\n)\ndb = client[\"database_name\"]\n\n\nclass User(BeanieBaseUser, Document):\n    pass\n\n\nasync def get_user_db():\n    yield BeanieUserDatabase(User)\n
from beanie import PydanticObjectId\nfrom fastapi_users import schemas\n\n\nclass UserRead(schemas.BaseUser[PydanticObjectId]):\n    pass\n\n\nclass UserCreate(schemas.BaseUserCreate):\n    pass\n\n\nclass UserUpdate(schemas.BaseUserUpdate):\n    pass\n
from typing import Optional\n\nfrom beanie import PydanticObjectId\nfrom fastapi import Depends, Request\nfrom fastapi_users import BaseUserManager, FastAPIUsers\nfrom fastapi_users.authentication import (\n    AuthenticationBackend,\n    BearerTransport,\n    JWTStrategy,\n)\nfrom fastapi_users.db import BeanieUserDatabase, ObjectIDIDMixin\n\nfrom app.db import User, get_user_db\n\nSECRET = \"SECRET\"\n\n\nclass UserManager(ObjectIDIDMixin, BaseUserManager[User, PydanticObjectId]):\n    reset_password_token_secret = SECRET\n    verification_token_secret = SECRET\n\n    async def on_after_register(self, user: User, request: Optional[Request] = None):\n        print(f\"User {user.id} has registered.\")\n\n    async def on_after_forgot_password(\n        self, user: User, token: str, request: Optional[Request] = None\n    ):\n        print(f\"User {user.id} has forgot their password. Reset token: {token}\")\n\n    async def on_after_request_verify(\n        self, user: User, token: str, request: Optional[Request] = None\n    ):\n        print(f\"Verification requested for user {user.id}. Verification token: {token}\")\n\n\nasync def get_user_manager(user_db: BeanieUserDatabase = Depends(get_user_db)):\n    yield UserManager(user_db)\n\n\nbearer_transport = BearerTransport(tokenUrl=\"auth/jwt/login\")\n\n\ndef get_jwt_strategy() -> JWTStrategy:\n    return JWTStrategy(secret=SECRET, lifetime_seconds=3600)\n\n\nauth_backend = AuthenticationBackend(\n    name=\"jwt\",\n    transport=bearer_transport,\n    get_strategy=get_jwt_strategy,\n)\n\nfastapi_users = FastAPIUsers[User, PydanticObjectId](get_user_manager, [auth_backend])\n\ncurrent_active_user = fastapi_users.current_user(active=True)\n
"},{"location":"configuration/full-example/#what-now","title":"What now?","text":"

You're ready to go! Be sure to check the Usage section to understand how to work with FastAPI Users.

"},{"location":"configuration/oauth/","title":"OAuth2","text":"

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

"},{"location":"configuration/oauth/#installation","title":"Installation","text":"

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

pip install 'fastapi-users[sqlalchemy,oauth]'\n
pip install 'fastapi-users[beanie,oauth]'\n
"},{"location":"configuration/oauth/#configuration","title":"Configuration","text":""},{"location":"configuration/oauth/#instantiate-an-oauth2-client","title":"Instantiate an OAuth2 client","text":"

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

from httpx_oauth.clients.google import GoogleOAuth2\n\ngoogle_oauth_client = GoogleOAuth2(\"CLIENT_ID\", \"CLIENT_SECRET\")\n
"},{"location":"configuration/oauth/#setup-the-database-adapter","title":"Setup the database adapter","text":""},{"location":"configuration/oauth/#sqlalchemy","title":"SQLAlchemy","text":"

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

from typing import AsyncGenerator, List\n\nfrom fastapi import Depends\nfrom fastapi_users.db import (\n    SQLAlchemyBaseOAuthAccountTableUUID,\n    SQLAlchemyBaseUserTableUUID,\n    SQLAlchemyUserDatabase,\n)\nfrom sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine\nfrom sqlalchemy.orm import DeclarativeBase, Mapped, relationship\n\nDATABASE_URL = \"sqlite+aiosqlite:///./test.db\"\n\n\nclass Base(DeclarativeBase):\n    pass\n\n\nclass OAuthAccount(SQLAlchemyBaseOAuthAccountTableUUID, Base):\n    pass\n\n\nclass User(SQLAlchemyBaseUserTableUUID, Base):\n    oauth_accounts: Mapped[List[OAuthAccount]] = relationship(\n        \"OAuthAccount\", lazy=\"joined\"\n    )\n\n\nengine = create_async_engine(DATABASE_URL)\nasync_session_maker = async_sessionmaker(engine, expire_on_commit=False)\n\n\nasync def create_db_and_tables():\n    async with engine.begin() as conn:\n        await conn.run_sync(Base.metadata.create_all)\n\n\nasync def get_async_session() -> AsyncGenerator[AsyncSession, None]:\n    async with async_session_maker() as session:\n        yield session\n\n\nasync def get_user_db(session: AsyncSession = Depends(get_async_session)):\n    yield SQLAlchemyUserDatabase(session, User, OAuthAccount)\n

Notice that we also manually added a relationship on User so that SQLAlchemy can properly retrieve the OAuth accounts of the user.

Besides, when instantiating the database adapter, we need pass this SQLAlchemy model as third argument.

Primary key is defined as UUID

By default, we use UUID as a primary key ID for your user. If you want to use another type, like an auto-incremented integer, you can use SQLAlchemyBaseOAuthAccountTable as base class and define your own id and user_id column.

class OAuthAccount(SQLAlchemyBaseOAuthAccountTable[int], Base):\n    id: Mapped[int] = mapped_column(Integer, primary_key=True)\n\n    @declared_attr\n    def user_id(cls) -> Mapped[int]:\n        return mapped_column(Integer, ForeignKey(\"user.id\", ondelete=\"cascade\"), nullable=False)\n

Notice that SQLAlchemyBaseOAuthAccountTable expects a generic type to define the actual type of ID you use.

"},{"location":"configuration/oauth/#beanie","title":"Beanie","text":"

The advantage of MongoDB is that you can easily embed sub-objects in a single document. That's why the configuration for Beanie is quite simple. All we need to do is to define another class to structure an OAuth account object.

from typing import List\n\nimport motor.motor_asyncio\nfrom beanie import Document\nfrom fastapi_users.db import BaseOAuthAccount, BeanieBaseUser, BeanieUserDatabase\nfrom pydantic import Field\n\nDATABASE_URL = \"mongodb://localhost:27017\"\nclient = motor.motor_asyncio.AsyncIOMotorClient(\n    DATABASE_URL, uuidRepresentation=\"standard\"\n)\ndb = client[\"database_name\"]\n\n\nclass OAuthAccount(BaseOAuthAccount):\n    pass\n\n\nclass User(BeanieBaseUser, Document):\n    oauth_accounts: List[OAuthAccount] = Field(default_factory=list)\n\n\nasync def get_user_db():\n    yield BeanieUserDatabase(User, OAuthAccount)\n

It's worth to note that OAuthAccount is not a Beanie document but a Pydantic model that we'll embed inside the User document, through the oauth_accounts array.

"},{"location":"configuration/oauth/#generate-routers","title":"Generate routers","text":"

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

app.include_router(\n    fastapi_users.get_oauth_router(google_oauth_client, auth_backend, \"SECRET\"),\n    prefix=\"/auth/google\",\n    tags=[\"auth\"],\n)\n

Tip

If you have several OAuth clients and/or several authentication backends, you'll need to create a router for each pair you want to support.

"},{"location":"configuration/oauth/#existing-account-association","title":"Existing account association","text":"

If a user with the same e-mail address already exists, an HTTP 400 error will be raised by default.

You can however choose to automatically link this OAuth account to the existing user account by setting the associate_by_email flag:

app.include_router(\n    fastapi_users.get_oauth_router(\n        google_oauth_client,\n        auth_backend,\n        \"SECRET\",\n        associate_by_email=True,\n    ),\n    prefix=\"/auth/google\",\n    tags=[\"auth\"],\n)\n

Bear in mind though that it can lead to security breaches if the OAuth provider does not validate e-mail addresses. How?

  • Let's say your app support an OAuth provider, Merlinbook, which does not validate e-mail addresses.
  • Imagine a user registers to your app with the e-mail address lancelot@camelot.bt.
  • Now, a malicious user creates an account on Merlinbook with the same e-mail address. Without e-mail validation, the malicious user can use this account without limitation.
  • The malicious user authenticates using Merlinbook OAuth on your app, which automatically associates to the existing lancelot@camelot.bt.
  • Now, the malicious user has full access to the user account on your app \ud83d\ude1e
"},{"location":"configuration/oauth/#association-router-for-authenticated-users","title":"Association router for authenticated users","text":"

We also provide a router to associate an already authenticated user with an OAuth account. After this association, the user will be able to authenticate with this OAuth provider.

app.include_router(\n    fastapi_users.get_oauth_associate_router(google_oauth_client, UserRead, \"SECRET\"),\n    prefix=\"/auth/associate/google\",\n    tags=[\"auth\"],\n)\n

Notice that, just like for the Users router, you have to pass the UserRead Pydantic schema.

"},{"location":"configuration/oauth/#set-is_verified-to-true-by-default","title":"Set is_verified to True by default","text":"

This section is only useful if you set up email verification

You can read more about this feature here.

When a new user registers with an OAuth provider, the is_verified flag is set to False, which requires the user to verify its email address.

You can choose to trust the email address given by the OAuth provider and set the is_verified flag to True after registration. You can do this by setting the is_verified_by_default argument:

app.include_router(\n    fastapi_users.get_oauth_router(\n        google_oauth_client,\n        auth_backend,\n        \"SECRET\",\n        is_verified_by_default=True,\n    ),\n    prefix=\"/auth/google\",\n    tags=[\"auth\"],\n)\n

Make sure you can trust the OAuth provider

Make sure the OAuth provider you're using does verify the email address before enabling this flag.

"},{"location":"configuration/oauth/#full-example","title":"Full example","text":"

Warning

Notice that SECRET should be changed to a strong passphrase. Insecure passwords may give attackers full access to your database.

"},{"location":"configuration/oauth/#sqlalchemy_1","title":"SQLAlchemy","text":"

Open

requirements.txtmain.pyapp/app.pyapp/db.pyapp/schemas.pyapp/users.py
fastapi\nfastapi-users[sqlalchemy,oauth]\nuvicorn[standard]\naiosqlite\n
import uvicorn\n\nif __name__ == \"__main__\":\n    uvicorn.run(\"app.app:app\", host=\"0.0.0.0\", log_level=\"info\")\n
from contextlib import asynccontextmanager\n\nfrom fastapi import Depends, FastAPI\n\nfrom app.db import User, create_db_and_tables\nfrom app.schemas import UserCreate, UserRead, UserUpdate\nfrom app.users import (\n    SECRET,\n    auth_backend,\n    current_active_user,\n    fastapi_users,\n    google_oauth_client,\n)\n\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    # Not needed if you setup a migration system like Alembic\n    await create_db_and_tables()\n    yield\n\n\napp = FastAPI(lifespan=lifespan)\n\napp.include_router(\n    fastapi_users.get_auth_router(auth_backend), prefix=\"/auth/jwt\", tags=[\"auth\"]\n)\napp.include_router(\n    fastapi_users.get_register_router(UserRead, UserCreate),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\napp.include_router(\n    fastapi_users.get_reset_password_router(),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\napp.include_router(\n    fastapi_users.get_verify_router(UserRead),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\napp.include_router(\n    fastapi_users.get_users_router(UserRead, UserUpdate),\n    prefix=\"/users\",\n    tags=[\"users\"],\n)\napp.include_router(\n    fastapi_users.get_oauth_router(google_oauth_client, auth_backend, SECRET),\n    prefix=\"/auth/google\",\n    tags=[\"auth\"],\n)\n\n\n@app.get(\"/authenticated-route\")\nasync def authenticated_route(user: User = Depends(current_active_user)):\n    return {\"message\": f\"Hello {user.email}!\"}\n
from typing import AsyncGenerator, List\n\nfrom fastapi import Depends\nfrom fastapi_users.db import (\n    SQLAlchemyBaseOAuthAccountTableUUID,\n    SQLAlchemyBaseUserTableUUID,\n    SQLAlchemyUserDatabase,\n)\nfrom sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine\nfrom sqlalchemy.orm import DeclarativeBase, Mapped, relationship\n\nDATABASE_URL = \"sqlite+aiosqlite:///./test.db\"\n\n\nclass Base(DeclarativeBase):\n    pass\n\n\nclass OAuthAccount(SQLAlchemyBaseOAuthAccountTableUUID, Base):\n    pass\n\n\nclass User(SQLAlchemyBaseUserTableUUID, Base):\n    oauth_accounts: Mapped[List[OAuthAccount]] = relationship(\n        \"OAuthAccount\", lazy=\"joined\"\n    )\n\n\nengine = create_async_engine(DATABASE_URL)\nasync_session_maker = async_sessionmaker(engine, expire_on_commit=False)\n\n\nasync def create_db_and_tables():\n    async with engine.begin() as conn:\n        await conn.run_sync(Base.metadata.create_all)\n\n\nasync def get_async_session() -> AsyncGenerator[AsyncSession, None]:\n    async with async_session_maker() as session:\n        yield session\n\n\nasync def get_user_db(session: AsyncSession = Depends(get_async_session)):\n    yield SQLAlchemyUserDatabase(session, User, OAuthAccount)\n
import uuid\n\nfrom fastapi_users import schemas\n\n\nclass UserRead(schemas.BaseUser[uuid.UUID]):\n    pass\n\n\nclass UserCreate(schemas.BaseUserCreate):\n    pass\n\n\nclass UserUpdate(schemas.BaseUserUpdate):\n    pass\n
import os\nimport uuid\nfrom typing import Optional\n\nfrom fastapi import Depends, Request\nfrom fastapi_users import BaseUserManager, FastAPIUsers, UUIDIDMixin, models\nfrom fastapi_users.authentication import (\n    AuthenticationBackend,\n    BearerTransport,\n    JWTStrategy,\n)\nfrom fastapi_users.db import SQLAlchemyUserDatabase\nfrom httpx_oauth.clients.google import GoogleOAuth2\n\nfrom app.db import User, get_user_db\n\nSECRET = \"SECRET\"\n\ngoogle_oauth_client = GoogleOAuth2(\n    os.getenv(\"GOOGLE_OAUTH_CLIENT_ID\", \"\"),\n    os.getenv(\"GOOGLE_OAUTH_CLIENT_SECRET\", \"\"),\n)\n\n\nclass UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):\n    reset_password_token_secret = SECRET\n    verification_token_secret = SECRET\n\n    async def on_after_register(self, user: User, request: Optional[Request] = None):\n        print(f\"User {user.id} has registered.\")\n\n    async def on_after_forgot_password(\n        self, user: User, token: str, request: Optional[Request] = None\n    ):\n        print(f\"User {user.id} has forgot their password. Reset token: {token}\")\n\n    async def on_after_request_verify(\n        self, user: User, token: str, request: Optional[Request] = None\n    ):\n        print(f\"Verification requested for user {user.id}. Verification token: {token}\")\n\n\nasync def get_user_manager(user_db: SQLAlchemyUserDatabase = Depends(get_user_db)):\n    yield UserManager(user_db)\n\n\nbearer_transport = BearerTransport(tokenUrl=\"auth/jwt/login\")\n\n\ndef get_jwt_strategy() -> JWTStrategy[models.UP, models.ID]:\n    return JWTStrategy(secret=SECRET, lifetime_seconds=3600)\n\n\nauth_backend = AuthenticationBackend(\n    name=\"jwt\",\n    transport=bearer_transport,\n    get_strategy=get_jwt_strategy,\n)\n\nfastapi_users = FastAPIUsers[User, uuid.UUID](get_user_manager, [auth_backend])\n\ncurrent_active_user = fastapi_users.current_user(active=True)\n
"},{"location":"configuration/oauth/#beanie_1","title":"Beanie","text":"

Open

requirements.txtmain.pyapp/app.pyapp/db.pyapp/schemas.pyapp/users.py
fastapi\nfastapi-users[beanie,oauth]\nuvicorn[standard]\n
import uvicorn\n\nif __name__ == \"__main__\":\n    uvicorn.run(\"app.app:app\", host=\"0.0.0.0\", log_level=\"info\")\n
from contextlib import asynccontextmanager\n\nfrom beanie import init_beanie\nfrom fastapi import Depends, FastAPI\n\nfrom app.db import User, db\nfrom app.schemas import UserCreate, UserRead, UserUpdate\nfrom app.users import (\n    SECRET,\n    auth_backend,\n    current_active_user,\n    fastapi_users,\n    google_oauth_client,\n)\n\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    await init_beanie(\n        database=db,\n        document_models=[\n            User,\n        ],\n    )\n    yield\n\n\napp = FastAPI(lifespan=lifespan)\n\napp.include_router(\n    fastapi_users.get_auth_router(auth_backend), prefix=\"/auth/jwt\", tags=[\"auth\"]\n)\napp.include_router(\n    fastapi_users.get_register_router(UserRead, UserCreate),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\napp.include_router(\n    fastapi_users.get_reset_password_router(),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\napp.include_router(\n    fastapi_users.get_verify_router(UserRead),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\napp.include_router(\n    fastapi_users.get_users_router(UserRead, UserUpdate),\n    prefix=\"/users\",\n    tags=[\"users\"],\n)\napp.include_router(\n    fastapi_users.get_oauth_router(google_oauth_client, auth_backend, SECRET),\n    prefix=\"/auth/google\",\n    tags=[\"auth\"],\n)\n\n\n@app.get(\"/authenticated-route\")\nasync def authenticated_route(user: User = Depends(current_active_user)):\n    return {\"message\": f\"Hello {user.email}!\"}\n
from typing import List\n\nimport motor.motor_asyncio\nfrom beanie import Document\nfrom fastapi_users.db import BaseOAuthAccount, BeanieBaseUser, BeanieUserDatabase\nfrom pydantic import Field\n\nDATABASE_URL = \"mongodb://localhost:27017\"\nclient = motor.motor_asyncio.AsyncIOMotorClient(\n    DATABASE_URL, uuidRepresentation=\"standard\"\n)\ndb = client[\"database_name\"]\n\n\nclass OAuthAccount(BaseOAuthAccount):\n    pass\n\n\nclass User(BeanieBaseUser, Document):\n    oauth_accounts: List[OAuthAccount] = Field(default_factory=list)\n\n\nasync def get_user_db():\n    yield BeanieUserDatabase(User, OAuthAccount)\n
from beanie import PydanticObjectId\nfrom fastapi_users import schemas\n\n\nclass UserRead(schemas.BaseUser[PydanticObjectId]):\n    pass\n\n\nclass UserCreate(schemas.BaseUserCreate):\n    pass\n\n\nclass UserUpdate(schemas.BaseUserUpdate):\n    pass\n
import os\nfrom typing import Optional\n\nfrom beanie import PydanticObjectId\nfrom fastapi import Depends, Request\nfrom fastapi_users import BaseUserManager, FastAPIUsers, models\nfrom fastapi_users.authentication import (\n    AuthenticationBackend,\n    BearerTransport,\n    JWTStrategy,\n)\nfrom fastapi_users.db import BeanieUserDatabase, ObjectIDIDMixin\nfrom httpx_oauth.clients.google import GoogleOAuth2\n\nfrom app.db import User, get_user_db\n\nSECRET = \"SECRET\"\n\ngoogle_oauth_client = GoogleOAuth2(\n    os.getenv(\"GOOGLE_OAUTH_CLIENT_ID\", \"\"),\n    os.getenv(\"GOOGLE_OAUTH_CLIENT_SECRET\", \"\"),\n)\n\n\nclass UserManager(ObjectIDIDMixin, BaseUserManager[User, PydanticObjectId]):\n    reset_password_token_secret = SECRET\n    verification_token_secret = SECRET\n\n    async def on_after_register(self, user: User, request: Optional[Request] = None):\n        print(f\"User {user.id} has registered.\")\n\n    async def on_after_forgot_password(\n        self, user: User, token: str, request: Optional[Request] = None\n    ):\n        print(f\"User {user.id} has forgot their password. Reset token: {token}\")\n\n    async def on_after_request_verify(\n        self, user: User, token: str, request: Optional[Request] = None\n    ):\n        print(f\"Verification requested for user {user.id}. Verification token: {token}\")\n\n\nasync def get_user_manager(user_db: BeanieUserDatabase = Depends(get_user_db)):\n    yield UserManager(user_db)\n\n\nbearer_transport = BearerTransport(tokenUrl=\"auth/jwt/login\")\n\n\ndef get_jwt_strategy() -> JWTStrategy[models.UP, models.ID]:\n    return JWTStrategy(secret=SECRET, lifetime_seconds=3600)\n\n\nauth_backend = AuthenticationBackend(\n    name=\"jwt\",\n    transport=bearer_transport,\n    get_strategy=get_jwt_strategy,\n)\n\nfastapi_users = FastAPIUsers[User, PydanticObjectId](get_user_manager, [auth_backend])\n\ncurrent_active_user = fastapi_users.current_user(active=True)\n
"},{"location":"configuration/overview/","title":"Overview","text":"

The schema below shows you how the library is structured and how each part fit together.

flowchart TB\n    FASTAPI_USERS{FastAPIUsers}\n    USER_MANAGER{UserManager}\n    USER_MODEL{User model}\n    DATABASE_DEPENDENCY[[get_user_db]]\n    USER_MANAGER_DEPENDENCY[[get_user_manager]]\n    CURRENT_USER[[current_user]]\n    subgraph SCHEMAS[Schemas]\n        USER[User]\n        USER_CREATE[UserCreate]\n        USER_UPDATE[UserUpdate]\n    end\n    subgraph DATABASE[Database adapters]\n        SQLALCHEMY[SQLAlchemy]\n        BEANIE[Beanie]\n    end\n    subgraph ROUTERS[Routers]\n        AUTH[[get_auth_router]]\n        OAUTH[[get_oauth_router]]\n        OAUTH_ASSOCIATE[[get_oauth_associate_router]]\n        REGISTER[[get_register_router]]\n        VERIFY[[get_verify_router]]\n        RESET[[get_reset_password_router]]\n        USERS[[get_users_router]]\n    end\n    subgraph AUTH_BACKENDS[Authentication]\n        subgraph TRANSPORTS[Transports]\n            COOKIE[CookieTransport]\n            BEARER[BearerTransport]\n        end\n        subgraph STRATEGIES[Strategies]\n            DB[DatabaseStrategy]\n            JWT[JWTStrategy]\n            REDIS[RedisStrategy]\n        end\n        AUTH_BACKEND{AuthenticationBackend}\n    end\n    DATABASE --> DATABASE_DEPENDENCY\n    USER_MODEL --> DATABASE_DEPENDENCY\n    DATABASE_DEPENDENCY --> USER_MANAGER\n\n    USER_MANAGER --> USER_MANAGER_DEPENDENCY\n    USER_MANAGER_DEPENDENCY --> FASTAPI_USERS\n\n    FASTAPI_USERS --> ROUTERS\n\n    TRANSPORTS --> AUTH_BACKEND\n    STRATEGIES --> AUTH_BACKEND\n\n    AUTH_BACKEND --> ROUTERS\n    AUTH_BACKEND --> FASTAPI_USERS\n\n    FASTAPI_USERS --> CURRENT_USER\n\n    SCHEMAS --> ROUTERS
"},{"location":"configuration/overview/#user-model-and-database-adapters","title":"User model and database adapters","text":"

FastAPI Users is compatible with various databases and ORM. To build the interface between those database tools and the library, we provide database adapters classes that you need to instantiate and configure.

\u27a1\ufe0f I'm using SQLAlchemy

\u27a1\ufe0f I'm using Beanie

"},{"location":"configuration/overview/#authentication-backends","title":"Authentication backends","text":"

Authentication backends define the way users sessions are managed in your app, like access tokens or cookies.

They are composed of two parts: a transport, which is how the token will be carried over the requests (e.g. cookies, headers...) and a strategy, which is how the token will be generated and secured (e.g. a JWT, a token in database...).

\u27a1\ufe0f Configure the authentication backends

"},{"location":"configuration/overview/#usermanager","title":"UserManager","text":"

The UserManager object bears most of the logic of FastAPI Users: registration, verification, password reset... We provide a BaseUserManager with this common logic; which you should overload to define how to validate passwords or handle events.

This UserManager object should be provided through a FastAPI dependency, get_user_manager.

\u27a1\ufe0f Configure UserManager

"},{"location":"configuration/overview/#schemas","title":"Schemas","text":"

FastAPI is heavily using Pydantic models to validate request payloads and serialize responses. FastAPI Users is no exception and will expect you to provide Pydantic schemas representing a user when it's read, created and updated.

\u27a1\ufe0f Configure schemas

"},{"location":"configuration/overview/#fastapiusers-and-routers","title":"FastAPIUsers and routers","text":"

Finally, FastAPIUsers object is the main class from which you'll be able to generate routers for classic routes like registration or login, but also get the current_user dependency factory to inject the authenticated user in your own routes.

\u27a1\ufe0f Configure FastAPIUsers and routers

"},{"location":"configuration/password-hash/","title":"Password hash","text":"

By default, FastAPI Users will use the Argon2 algorithm to hash and salt passwords before storing them in the database, with backwards-compatibility with Bcrypt.

The implementation is provided by pwdlib, a modern password hashing wrapper.

"},{"location":"configuration/password-hash/#customize-passwordhash","title":"Customize PasswordHash","text":"

If you need to tune the algorithms used or their settings, you can customize the PasswordHash object of pwdlib.

For this, you'll need to instantiate the PasswordHelper class and pass it your PasswordHash. The example below shows you how you can create a PasswordHash to only support the Argon2 algorithm.

from fastapi_users.password import PasswordHelper\nfrom pwdlib import PasswordHash, exceptions\nfrom pwdlib.hashers.argon2 import Argon2Hasher\n\npassword_hash = PasswordHash((\n    Argon2Hasher(),\n))\npassword_helper = PasswordHelper(password_hash)\n

Finally, pass the password_helper variable while instantiating your UserManager:

async def get_user_manager(user_db=Depends(get_user_db)):\n    yield UserManager(user_db, password_helper)\n

Password hashes are automatically upgraded

FastAPI Users takes care of upgrading the password hash to a more recent algorithm when needed.

Typically, when a user logs in, we'll check if the password hash algorithm is deprecated.

If it is, we take the opportunity of having the password in plain-text at hand (since the user just logged in!) to hash it with a better algorithm and update it in database.

"},{"location":"configuration/password-hash/#full-customization","title":"Full customization","text":"

If you don't wish to use pwdlib at all \u2013 which we don't recommend unless you're absolutely sure of what you're doing \u2014 you can implement your own PasswordHelper class as long as it implements the PasswordHelperProtocol and its methods.

from typing import Tuple\n\nfrom fastapi_users.password import PasswordHelperProtocol\n\nclass PasswordHelper(PasswordHelperProtocol):\n    def verify_and_update(\n        self, plain_password: str, hashed_password: str\n    ) -> Tuple[bool, str]:\n        ...\n\n    def hash(self, password: str) -> str:\n        ...\n\n    def generate(self) -> str:\n        ...\n
"},{"location":"configuration/schemas/","title":"Schemas","text":"

FastAPI is heavily using Pydantic models to validate request payloads and serialize responses. FastAPI Users is no exception and will expect you to provide Pydantic schemas representing a user when it's read, created and updated.

It's different from your User model, which is an object that actually interacts with the database. Those schemas on the other hand are here to validate data and correctly serialize it in the API.

FastAPI Users provides a base structure to cover its needs. It is structured like this:

  • id (ID) \u2013 Unique identifier of the user. It matches the type of your ID, like UUID or integer.
  • email (str) \u2013 Email of the user. Validated by email-validator.
  • is_active (bool) \u2013 Whether or not the user is active. If not, login and forgot password requests will be denied. Defaults to True.
  • is_verified (bool) \u2013 Whether or not the user is verified. Optional but helpful with the verify router logic. Defaults to False.
  • is_superuser (bool) \u2013 Whether or not the user is a superuser. Useful to implement administration logic. Defaults to False.
"},{"location":"configuration/schemas/#define-your-schemas","title":"Define your schemas","text":"

There are four Pydantic models variations provided as mixins:

  • BaseUser, which provides the basic fields and validation;
  • BaseCreateUser, dedicated to user registration, which consists of compulsory email and password fields;
  • BaseUpdateUser, dedicated to user profile update, which adds an optional password field;

You should define each of those variations, inheriting from each mixin:

import uuid\n\nfrom fastapi_users import schemas\n\n\nclass UserRead(schemas.BaseUser[uuid.UUID]):\n    pass\n\n\nclass UserCreate(schemas.BaseUserCreate):\n    pass\n\n\nclass UserUpdate(schemas.BaseUserUpdate):\n    pass\n

Typing: ID generic type is expected

You can see that we define a generic type when extending the BaseUser class. It should correspond to the type of ID you use on your model. Here, we chose UUID, but it can be anything, like an integer or a MongoDB ObjectID.

"},{"location":"configuration/schemas/#adding-your-own-fields","title":"Adding your own fields","text":"

You can of course add your own properties there to fit to your needs. In the example below, we add a required string property, first_name, and an optional date property, birthdate.

import datetime\nimport uuid\n\nfrom fastapi_users import schemas\n\n\nclass UserRead(schemas.BaseUser[uuid.UUID]):\n    first_name: str\n    birthdate: Optional[datetime.date]\n\n\nclass UserCreate(schemas.BaseUserCreate):\n    first_name: str\n    birthdate: Optional[datetime.date]\n\n\nclass UserUpdate(schemas.BaseUserUpdate):\n    first_name: Optional[str]\n    birthdate: Optional[datetime.date]\n

Make sure to mirror this in your database model

The User model you defined earlier for your specific database will be the central object that will actually store the data. Therefore, you need to define the very same fields in it so the data can be actually stored.

"},{"location":"configuration/user-manager/","title":"UserManager","text":"

The UserManager class is the core logic of FastAPI Users. We provide the BaseUserManager class which you should extend to set some parameters and define logic, for example when a user just registered or forgot its password.

It's designed to be easily extensible and customizable so that you can integrate your very own logic.

"},{"location":"configuration/user-manager/#create-your-usermanager-class","title":"Create your UserManager class","text":"

You should define your own version of the UserManager class to set various parameters.

import uuid\nfrom typing import Optional\n\nfrom fastapi import Depends, Request\nfrom fastapi_users import BaseUserManager, UUIDIDMixin\n\nfrom .db import User, get_user_db\n\nSECRET = \"SECRET\"\n\n\nclass UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):\n    reset_password_token_secret = SECRET\n    verification_token_secret = SECRET\n\n    async def on_after_register(self, user: User, request: Optional[Request] = None):\n        print(f\"User {user.id} has registered.\")\n\n    async def on_after_forgot_password(\n        self, user: User, token: str, request: Optional[Request] = None\n    ):\n        print(f\"User {user.id} has forgot their password. Reset token: {token}\")\n\n    async def on_after_request_verify(\n        self, user: User, token: str, request: Optional[Request] = None\n    ):\n        print(f\"Verification requested for user {user.id}. Verification token: {token}\")\n\n\nasync def get_user_manager(user_db=Depends(get_user_db)):\n    yield UserManager(user_db)\n

As you can see, you have to define here various attributes and methods. You can find the complete list of those below.

Typing: User and ID generic types are expected

You can see that we define two generic types when extending the base class:

  • User, which is the user model we defined in the database part
  • The ID, which should correspond to the type of ID you use on your model. Here, we chose UUID, but it can be anything, like an integer or a MongoDB ObjectID.

It'll help you to have good type-checking and auto-completion when implementing the custom methods.

"},{"location":"configuration/user-manager/#the-id-parser-mixin","title":"The ID parser mixin","text":"

Since the user ID is fully generic, we need a way to parse it reliably when it'll come from API requests, typically as URL path attributes.

That's why we added the UUIDIDMixin in the example above. It implements the parse_id method, ensuring UUID are valid and correctly parsed.

Of course, it's important that this logic matches the type of your ID. To help you with this, we provide mixins for the most common cases:

  • UUIDIDMixin, for UUID ID.
  • IntegerIDMixin, for integer ID.
  • ObjectIDIDMixin (provided by fastapi_users_db_beanie), for MongoDB ObjectID.

Inheritance order matters

Notice in your example that the mixin comes first in our UserManager inheritance. Because of the Method-Resolution-Order (MRO) of Python, the left-most element takes precedence.

If you need another type of ID, you can simply overload the parse_id method on your UserManager class:

from fastapi_users import BaseUserManager, InvalidID\n\n\nclass UserManager(BaseUserManager[User, MyCustomID]):\n    def parse_id(self, value: Any) -> MyCustomID:\n        try:\n            return MyCustomID(value)\n        except ValueError as e:\n            raise InvalidID() from e  # (1)!\n
  1. If the ID can't be parsed into the desired type, you'll need to raise an InvalidID exception.
"},{"location":"configuration/user-manager/#create-get_user_manager-dependency","title":"Create get_user_manager dependency","text":"

The UserManager class will be injected at runtime using a FastAPI dependency. This way, you can run it in a database session or swap it with a mock during testing.

import uuid\nfrom typing import Optional\n\nfrom fastapi import Depends, Request\nfrom fastapi_users import BaseUserManager, UUIDIDMixin\n\nfrom .db import User, get_user_db\n\nSECRET = \"SECRET\"\n\n\nclass UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):\n    reset_password_token_secret = SECRET\n    verification_token_secret = SECRET\n\n    async def on_after_register(self, user: User, request: Optional[Request] = None):\n        print(f\"User {user.id} has registered.\")\n\n    async def on_after_forgot_password(\n        self, user: User, token: str, request: Optional[Request] = None\n    ):\n        print(f\"User {user.id} has forgot their password. Reset token: {token}\")\n\n    async def on_after_request_verify(\n        self, user: User, token: str, request: Optional[Request] = None\n    ):\n        print(f\"Verification requested for user {user.id}. Verification token: {token}\")\n\n\nasync def get_user_manager(user_db=Depends(get_user_db)):\n    yield UserManager(user_db)\n

Notice that we use the get_user_db dependency we defined earlier to inject the database instance.

"},{"location":"configuration/user-manager/#customize-attributes-and-methods","title":"Customize attributes and methods","text":""},{"location":"configuration/user-manager/#attributes","title":"Attributes","text":"
  • reset_password_token_secret: Secret to encode reset password token. Use a strong passphrase and keep it secure.
  • reset_password_token_lifetime_seconds: Lifetime of reset password token. Defaults to 3600.
  • reset_password_token_audience: JWT audience of reset password token. Defaults to fastapi-users:reset.
  • verification_token_secret: Secret to encode verification token. Use a strong passphrase and keep it secure.
  • verification_token_lifetime_seconds: Lifetime of verification token. Defaults to 3600.
  • verification_token_audience: JWT audience of verification token. Defaults to fastapi-users:verify.
"},{"location":"configuration/user-manager/#methods","title":"Methods","text":""},{"location":"configuration/user-manager/#validate_password","title":"validate_password","text":"

Validate a password.

Arguments

  • password (str): the password to validate.
  • user (Union[UserCreate, User]): user model which we are currently validating the password. Useful if you want to check that the password doesn't contain the name or the birthdate of the user for example.

Output

This function should return None if the password is valid or raise InvalidPasswordException if not. This exception expects an argument reason telling why the password is invalid. It'll be part of the error response.

Example

from fastapi_users import BaseUserManager, InvalidPasswordException, UUIDIDMixin\n\n\nclass UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):\n    # ...\n    async def validate_password(\n        self,\n        password: str,\n        user: Union[UserCreate, User],\n    ) -> None:\n        if len(password) < 8:\n            raise InvalidPasswordException(\n                reason=\"Password should be at least 8 characters\"\n            )\n        if user.email in password:\n            raise InvalidPasswordException(\n                reason=\"Password should not contain e-mail\"\n            )\n
"},{"location":"configuration/user-manager/#on_after_register","title":"on_after_register","text":"

Perform logic after successful user registration.

Typically, you'll want to send a welcome e-mail or add it to your marketing analytics pipeline.

Arguments

  • user (User): the registered user.
  • request (Optional[Request]): optional FastAPI request object that triggered the operation. Defaults to None.

Example

from fastapi_users import BaseUserManager, UUIDIDMixin\n\n\nclass UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):\n    # ...\n    async def on_after_register(self, user: User, request: Optional[Request] = None):\n        print(f\"User {user.id} has registered.\")\n
"},{"location":"configuration/user-manager/#on_after_update","title":"on_after_update","text":"

Perform logic after successful user update.

It may be useful, for example, if you wish to update your user in a data analytics or customer success platform.

Arguments

  • user (User): the updated user.
  • update_dict (Dict[str, Any]): dictionary with the updated user fields.
  • request (Optional[Request]): optional FastAPI request object that triggered the operation. Defaults to None.

Example

from fastapi_users import BaseUserManager, UUIDIDMixin\n\n\nclass UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):\n    # ...\n    async def on_after_update(\n        self,\n        user: User,\n        update_dict: Dict[str, Any],\n        request: Optional[Request] = None,\n    ):\n        print(f\"User {user.id} has been updated with {update_dict}.\")\n
"},{"location":"configuration/user-manager/#on_after_login","title":"on_after_login","text":"

Perform logic after a successful user login.

It may be useful for custom logic or processes triggered by new logins, for example a daily login reward or for analytics.

Arguments

  • user (User): the updated user.
  • request (Optional[Request]): optional FastAPI request object that triggered the operation. Defaults to None.
  • response (Optional[Response]): Optional response built by the transport. Defaults to None.

Example

from fastapi_users import BaseUserManager, UUIDIDMixin\n\n\nclass UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):\n    # ...\n    async def on_after_login(\n        self,\n        user: User,\n        request: Optional[Request] = None,\n        response: Optional[Response] = None,\n    ):\n        print(f\"User {user.id} logged in.\")\n
"},{"location":"configuration/user-manager/#on_after_request_verify","title":"on_after_request_verify","text":"

Perform logic after successful verification request.

Typically, you'll want to send an e-mail with the link (and the token) that allows the user to verify their e-mail.

Arguments

  • user (User): the user to verify.
  • token (str): the verification token.
  • request (Optional[Request]): optional FastAPI request object that triggered the operation. Defaults to None.

Example

from fastapi_users import BaseUserManager, UUIDIDMixin\n\n\nclass UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):\n    # ...\n    async def on_after_request_verify(\n        self, user: User, token: str, request: Optional[Request] = None\n    ):\n        print(f\"Verification requested for user {user.id}. Verification token: {token}\")\n
"},{"location":"configuration/user-manager/#on_after_verify","title":"on_after_verify","text":"

Perform logic after successful user verification.

This may be useful if you wish to send another e-mail or store this information in a data analytics or customer success platform.

Arguments

  • user (User): the verified user.
  • request (Optional[Request]): optional FastAPI request object that triggered the operation. Defaults to None.

Example

from fastapi_users import BaseUserManager, UUIDIDMixin\n\n\nclass UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):\n    # ...\n    async def on_after_verify(\n        self, user: User, request: Optional[Request] = None\n    ):\n        print(f\"User {user.id} has been verified\")\n
"},{"location":"configuration/user-manager/#on_after_forgot_password","title":"on_after_forgot_password","text":"

Perform logic after successful forgot password request.

Typically, you'll want to send an e-mail with the link (and the token) that allows the user to reset their password.

Arguments

  • user (User): the user that forgot its password.
  • token (str): the forgot password token
  • request (Optional[Request]): optional FastAPI request object that triggered the operation. Defaults to None.

Example

from fastapi_users import BaseUserManager, UUIDIDMixin\n\n\nclass UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):\n    # ...\n    async def on_after_forgot_password(\n        self, user: User, token: str, request: Optional[Request] = None\n    ):\n        print(f\"User {user.id} has forgot their password. Reset token: {token}\")\n
"},{"location":"configuration/user-manager/#on_after_reset_password","title":"on_after_reset_password","text":"

Perform logic after successful password reset.

For example, you may want to send an e-mail to the concerned user to warn him that their password has been changed and that they should take action if they think they have been hacked.

Arguments

  • user (User): the user that reset its password.
  • request (Optional[Request]): optional FastAPI request object that triggered the operation. Defaults to None.

Example

from fastapi_users import BaseUserManager, UUIDIDMixin\n\n\nclass UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):\n    # ...\n    async def on_after_reset_password(self, user: User, request: Optional[Request] = None):\n        print(f\"User {user.id} has reset their password.\")\n
"},{"location":"configuration/user-manager/#on_before_delete","title":"on_before_delete","text":"

Perform logic before user delete.

For example, you may want to valide user resource integrity to see if any related user resource need to be marked inactive, or delete them recursively.

Arguments

  • user (User): the user to be deleted.
  • request (Optional[Request]): optional FastAPI request object that triggered the operation. Defaults to None.

Example

from fastapi_users import BaseUserManager, UUIDIDMixin\n\n\nclass UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):\n    # ...\n    async def on_before_delete(self, user: User, request: Optional[Request] = None):\n        print(f\"User {user.id} is going to be deleted\")\n
"},{"location":"configuration/user-manager/#on_after_delete","title":"on_after_delete","text":"

Perform logic after user delete.

For example, you may want to send an email to the administrator about the event.

Arguments

  • user (User): the user to be deleted.
  • request (Optional[Request]): optional FastAPI request object that triggered the operation. Defaults to None.

Example

from fastapi_users import BaseUserManager, UUIDIDMixin\n\n\nclass UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):\n    # ...\n    async def on_after_delete(self, user: User, request: Optional[Request] = None):\n        print(f\"User {user.id} is successfully deleted\")\n
"},{"location":"configuration/authentication/","title":"Authentication","text":"

FastAPI Users allows you to plug in several authentication methods.

"},{"location":"configuration/authentication/#how-it-works","title":"How it works?","text":"

You can have several authentication methods, e.g. a cookie authentication for browser-based queries and a JWT token authentication for pure API queries.

When checking authentication, each method is run one after the other. The first method yielding a user wins. If no method yields a user, an HTTPException is raised.

For each backend, you'll be able to add a router with the corresponding /login and /logout. More on this in the routers documentation.

"},{"location":"configuration/authentication/#transport-strategy-authentication-backend","title":"Transport + Strategy = Authentication backend","text":"

An authentication backend is composed of two parts:

"},{"location":"configuration/authentication/#transport","title":"Transport","text":"

It manages how the token will be carried over the request. We currently provide two methods:

"},{"location":"configuration/authentication/#bearer","title":"Bearer","text":"

The token will be sent through an Authorization: Bearer header.

Pros and cons

  • \u2705 Easy to read and set in every requests.
  • \u274c Needs to be stored manually somewhere in the client.

\u27a1\ufe0f Use it if you want to implement a mobile application or a pure REST API.

"},{"location":"configuration/authentication/#cookie","title":"Cookie","text":"

The token will be sent through a cookie.

Pros and cons

  • \u2705 Automatically stored and sent securely by web browsers in every requests.
  • \u2705 Automatically removed at expiration by web browsers.
  • \u274c Needs a CSRF protection for maximum security.
  • \u274c Harder to work with outside a browser, like a mobile app or a server.

\u27a1\ufe0f Use it if you want to implement a web frontend.

"},{"location":"configuration/authentication/#strategy","title":"Strategy","text":"

It manages how the token is generated and secured. We currently provide three methods:

"},{"location":"configuration/authentication/#jwt","title":"JWT","text":"

The token is self-contained in a JSON Web Token.

Pros and cons

  • \u2705 Self-contained: it doesn't need to be stored in a database.
  • \u274c Can't be invalidated on the server-side: it's valid until it expires.

\u27a1\ufe0f Use it if you want to get up-and-running quickly.

"},{"location":"configuration/authentication/#database","title":"Database","text":"

The token is stored in a table (or collection) in your database.

Pros and cons

  • \u2705 Secure and performant.
  • \u2705 Tokens can be invalidated server-side by removing them from the database.
  • \u2705 Highly customizable: add your own fields, create an API to retrieve the active sessions of your users, etc.
  • \u274c Configuration is a bit more complex.

\u27a1\ufe0f Use it if you want maximum flexibility in your token management.

"},{"location":"configuration/authentication/#redis","title":"Redis","text":"

The token is stored in a Redis key-store.

Pros and cons

  • \u2705 Secure and performant.
  • \u2705 Tokens can be invalidated server-side by removing them from Redis.
  • \u274c A Redis server is needed.

\u27a1\ufe0f Use it if you want maximum performance while being able to invalidate tokens.

"},{"location":"configuration/authentication/backend/","title":"Create a backend","text":"

As we said, a backend is the combination of a transport and a strategy. That way, you can create a complete strategy exactly fitting your needs.

For this, you have to use the AuthenticationBackend class.

from fastapi_users.authentication import AuthenticationBackend, BearerTransport, JWTStrategy\n\nSECRET = \"SECRET\"\n\nbearer_transport = BearerTransport(tokenUrl=\"auth/jwt/login\")\n\ndef get_jwt_strategy() -> JWTStrategy:\n    return JWTStrategy(secret=SECRET, lifetime_seconds=3600)\n\nauth_backend = AuthenticationBackend(\n    name=\"jwt\",\n    transport=bearer_transport,\n    get_strategy=get_jwt_strategy,\n)\n

As you can see, instantiation is quite simple. It accepts the following arguments:

  • name (str): Name of the backend. Each backend should have a unique name.
  • transport (Transport): An instance of a Transport class.
  • get_strategy (Callable[..., Strategy]): A dependency callable returning an instance of a Strategy class.
"},{"location":"configuration/authentication/backend/#next-steps","title":"Next steps","text":"

You can have as many authentication backends as you wish. You'll then have to pass those backends to your FastAPIUsers instance and generate an auth router for each one of them.

"},{"location":"configuration/authentication/strategies/database/","title":"Database","text":"

The most natural way for storing tokens is of course the very same database you're using for your application. In this strategy, we set up a table (or collection) for storing those tokens with the associated user id. On each request, we try to retrieve this token from the database to get the corresponding user id.

"},{"location":"configuration/authentication/strategies/database/#configuration","title":"Configuration","text":"

The configuration of this strategy is a bit more complex than the others as it requires you to configure models and a database adapter, exactly like we did for users.

"},{"location":"configuration/authentication/strategies/database/#database-adapters","title":"Database adapters","text":"

An access token will be structured like this in your database:

  • token (str) \u2013 Unique identifier of the token. It's generated automatically upon login by the strategy.
  • user_id (ID) \u2013 User id. of the user associated to this token.
  • created_at (datetime) \u2013 Date and time of creation of the token. It's used to determine if the token is expired or not.

We are providing a base model with those fields for each database we are supporting.

"},{"location":"configuration/authentication/strategies/database/#sqlalchemy","title":"SQLAlchemy","text":"

We'll expand from the basic SQLAlchemy configuration.

from typing import AsyncGenerator\n\nfrom fastapi import Depends\nfrom fastapi_users.db import SQLAlchemyBaseUserTableUUID, SQLAlchemyUserDatabase\nfrom fastapi_users_db_sqlalchemy.access_token import (\n    SQLAlchemyAccessTokenDatabase,\n    SQLAlchemyBaseAccessTokenTableUUID,\n)\nfrom sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine\nfrom sqlalchemy.orm import DeclarativeBase\n\nDATABASE_URL = \"sqlite+aiosqlite:///./test.db\"\n\n\nclass Base(DeclarativeBase):\n    pass\n\n\nclass User(SQLAlchemyBaseUserTableUUID, Base):\n    pass\n\n\nclass AccessToken(SQLAlchemyBaseAccessTokenTableUUID, Base):  # (1)!\n    pass\n\n\nengine = create_async_engine(DATABASE_URL)\nasync_session_maker = async_sessionmaker(engine, expire_on_commit=False)\n\n\nasync def create_db_and_tables():\n    async with engine.begin() as conn:\n        await conn.run_sync(Base.metadata.create_all)\n\n\nasync def get_async_session() -> AsyncGenerator[AsyncSession, None]:\n    async with async_session_maker() as session:\n        yield session\n\n\nasync def get_user_db(session: AsyncSession = Depends(get_async_session)):\n    yield SQLAlchemyUserDatabase(session, User)\n\n\nasync def get_access_token_db(\n    session: AsyncSession = Depends(get_async_session),\n):  # (2)!\n    yield SQLAlchemyAccessTokenDatabase(session, AccessToken)\n
  1. We define an AccessToken ORM model inheriting from SQLAlchemyBaseAccessTokenTableUUID.

  2. We define a dependency to instantiate the SQLAlchemyAccessTokenDatabase class. Just like the user database adapter, it expects a fresh SQLAlchemy session and the AccessToken model class we defined above.

user_id foreign key is defined as UUID

By default, we use UUID as a primary key ID for your user, so we follow the same convention to define the foreign key pointing to the user.

If you want to use another type, like an auto-incremented integer, you can use SQLAlchemyBaseAccessTokenTable as base class and define your own user_id column.

class AccessToken(SQLAlchemyBaseAccessTokenTable[int], Base):\n    @declared_attr\n    def user_id(cls) -> Mapped[int]:\n        return mapped_column(Integer, ForeignKey(\"user.id\", ondelete=\"cascade\"), nullable=False)\n

Notice that SQLAlchemyBaseAccessTokenTable expects a generic type to define the actual type of ID you use.

"},{"location":"configuration/authentication/strategies/database/#beanie","title":"Beanie","text":"

We'll expand from the basic Beanie configuration.

import motor.motor_asyncio\nfrom beanie import Document\nfrom fastapi_users.db import BeanieBaseUser, BeanieUserDatabase\nfrom fastapi_users_db_beanie.access_token import (\n    BeanieAccessTokenDatabase,\n    BeanieBaseAccessToken,\n)\n\nDATABASE_URL = \"mongodb://localhost:27017\"\nclient = motor.motor_asyncio.AsyncIOMotorClient(\n    DATABASE_URL, uuidRepresentation=\"standard\"\n)\ndb = client[\"database_name\"]\n\n\nclass User(BeanieBaseUser, Document):\n    pass\n\n\nclass AccessToken(BeanieBaseAccessToken, Document):  # (1)!\n    pass\n\n\nasync def get_user_db():\n    yield BeanieUserDatabase(User)\n\n\nasync def get_access_token_db():  # (2)!\n    yield BeanieAccessTokenDatabase(AccessToken)\n
  1. We define an AccessToken ODM model inheriting from BeanieBaseAccessToken. Notice that we set a generic type to define the type of the user_id reference. By default, it's a standard MongoDB ObjectID.

  2. We define a dependency to instantiate the BeanieAccessTokenDatabase class. Just like the user database adapter, it expects the AccessToken model class we defined above.

Don't forget to add the AccessToken ODM model to the document_models array in your Beanie initialization, just like you did with the User model!

Info

If you want to add your own custom settings to your AccessToken document model - like changing the collection name - don't forget to let your inner Settings class inherit the pre-defined settings from BeanieBaseAccessToken like this: Settings(BeanieBaseAccessToken.Settings): # ...! See Beanie's documentation on Settings for details.

"},{"location":"configuration/authentication/strategies/database/#strategy","title":"Strategy","text":"
import uuid\n\nfrom fastapi import Depends\nfrom fastapi_users.authentication.strategy.db import AccessTokenDatabase, DatabaseStrategy\n\nfrom .db import AccessToken, User\n\n\ndef get_database_strategy(\n    access_token_db: AccessTokenDatabase[AccessToken] = Depends(get_access_token_db),\n) -> DatabaseStrategy:\n    return DatabaseStrategy(access_token_db, lifetime_seconds=3600)\n

As you can see, instantiation is quite simple. It accepts the following arguments:

  • database (AccessTokenDatabase): A database adapter instance for AccessToken table, like we defined above.
  • lifetime_seconds (int): The lifetime of the token in seconds.

Why it's inside a function?

To allow strategies to be instantiated dynamically with other dependencies, they have to be provided as a callable to the authentication backend.

As you can see here, this pattern allows us to dynamically inject a connection to the database.

"},{"location":"configuration/authentication/strategies/database/#logout","title":"Logout","text":"

On logout, this strategy will delete the token from the database.

"},{"location":"configuration/authentication/strategies/jwt/","title":"JWT","text":"

JSON Web Token (JWT) is an internet standard for creating access tokens based on JSON. They don't need to be stored in a database: the data is self-contained inside and cryptographically signed.

"},{"location":"configuration/authentication/strategies/jwt/#configuration","title":"Configuration","text":"
from fastapi_users.authentication import JWTStrategy\n\nSECRET = \"SECRET\"\n\ndef get_jwt_strategy() -> JWTStrategy:\n    return JWTStrategy(secret=SECRET, lifetime_seconds=3600)\n

As you can see, instantiation is quite simple. It accepts the following arguments:

  • secret (Union[str, pydantic.SecretStr]): A constant secret which is used to encode the token. Use a strong passphrase and keep it secure.
  • lifetime_seconds (Optional[int]): The lifetime of the token in seconds. Can be set to None but in this case the token will be valid forever; which may raise serious security concerns.
  • token_audience (Optional[List[str]]): A list of valid audiences for the JWT token. Defaults to [\"fastapi-users:auth\"].
  • algorithm (Optional[str]): The JWT encryption algorithm. See RFC 7519, section 8. Defaults to \"HS256\".
  • public_key (Optional[Union[str, pydantic.SecretStr]]): If the JWT encryption algorithm requires a key pair instead of a simple secret, the key to decrypt the JWT may be provided here. The secret parameter will always be used to encrypt the JWT.

Why it's inside a function?

To allow strategies to be instantiated dynamically with other dependencies, they have to be provided as a callable to the authentication backend.

For JWTStrategy, since it doesn't require dependencies, it can be as simple as the function above.

"},{"location":"configuration/authentication/strategies/jwt/#rs256-example","title":"RS256 example","text":"
from fastapi_users.authentication import JWTStrategy\n\nPUBLIC_KEY = \"\"\"-----BEGIN PUBLIC KEY-----\n# Your RSA public key in PEM format goes here\n-----END PUBLIC KEY-----\"\"\"\n\nPRIVATE_KEY = \"\"\"-----BEGIN RSA PRIVATE KEY-----\n# Your RSA private key in PEM format goes here\n-----END RSA PRIVATE KEY-----\"\"\"\n\ndef get_jwt_strategy() -> JWTStrategy:\n    return JWTStrategy(\n        secret=PRIVATE_KEY, \n        lifetime_seconds=3600,\n        algorithm=\"RS256\",\n        public_key=PUBLIC_KEY,\n    )\n
"},{"location":"configuration/authentication/strategies/jwt/#logout","title":"Logout","text":"

On logout, this strategy won't do anything. Indeed, a JWT can't be invalidated on the server-side: it's valid until it expires.

"},{"location":"configuration/authentication/strategies/redis/","title":"Redis","text":"

Redis is an ultra-fast key-store database. As such, it's a good candidate for token management. In this strategy, a token is generated and associated with the user id. in the database. On each request, we try to retrieve this token from Redis to get the corresponding user id.

"},{"location":"configuration/authentication/strategies/redis/#installation","title":"Installation","text":"

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

pip install 'fastapi-users[redis]'\n
"},{"location":"configuration/authentication/strategies/redis/#configuration","title":"Configuration","text":"
import redis.asyncio\nfrom fastapi_users.authentication import RedisStrategy\n\nredis = redis.asyncio.from_url(\"redis://localhost:6379\", decode_responses=True)\n\ndef get_redis_strategy() -> RedisStrategy:\n    return RedisStrategy(redis, lifetime_seconds=3600)\n

As you can see, instantiation is quite simple. It accepts the following arguments:

  • redis (redis.asyncio.Redis): An instance of redis.asyncio.Redis. Note that the decode_responses flag set to True is necessary.
  • lifetime_seconds (Optional[int]): The lifetime of the token in seconds. Defaults to None, which means the token doesn't expire.
  • key_prefix (str): The prefix used to set the key in the Redis stored. Defaults to fastapi_users_token:.

Why it's inside a function?

To allow strategies to be instantiated dynamically with other dependencies, they have to be provided as a callable to the authentication backend.

"},{"location":"configuration/authentication/strategies/redis/#logout","title":"Logout","text":"

On logout, this strategy will delete the token from the Redis store.

"},{"location":"configuration/authentication/transports/bearer/","title":"Bearer","text":"

With this transport, the token is expected inside the Authorization header of the HTTP request with the Bearer scheme. It's particularly suited for pure API interaction or mobile apps.

"},{"location":"configuration/authentication/transports/bearer/#configuration","title":"Configuration","text":"
from fastapi_users.authentication import BearerTransport\n\nbearer_transport = BearerTransport(tokenUrl=\"auth/jwt/login\")\n

As you can see, instantiation is quite simple. It accepts the following arguments:

  • tokenUrl (str): The exact path of your login endpoint. It'll allow the interactive documentation to automatically discover it and get a working Authorize button. In most cases, you'll probably need a relative path, not absolute. You can read more details about this in the FastAPI documentation.
"},{"location":"configuration/authentication/transports/bearer/#login","title":"Login","text":"

This method will return the in the following form upon successful login:

200 OK

{\n    \"access_token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiOTIyMWZmYzktNjQwZi00MzcyLTg2ZDMtY2U2NDJjYmE1NjAzIiwiYXVkIjoiZmFzdGFwaS11c2VyczphdXRoIiwiZXhwIjoxNTcxNTA0MTkzfQ.M10bjOe45I5Ncu_uXvOmVV8QxnL-nZfcH96U90JaocI\",\n    \"token_type\": \"bearer\"\n}\n

Check documentation about login route.

"},{"location":"configuration/authentication/transports/bearer/#logout","title":"Logout","text":"

204 No content

"},{"location":"configuration/authentication/transports/bearer/#authentication","title":"Authentication","text":"

This method expects that you provide a Bearer authentication with a valid token corresponding to your strategy.

curl http://localhost:9000/protected-route -H'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiOTIyMWZmYzktNjQwZi00MzcyLTg2ZDMtY2U2NDJjYmE1NjAzIiwiYXVkIjoiZmFzdGFwaS11c2VyczphdXRoIiwiZXhwIjoxNTcxNTA0MTkzfQ.M10bjOe45I5Ncu_uXvOmVV8QxnL-nZfcH96U90JaocI'\n
"},{"location":"configuration/authentication/transports/cookie/","title":"Cookie","text":"

Cookies are an easy way to store stateful information into the user browser. Thus, it is more useful for browser-based navigation (e.g. a front-end app making API requests) rather than pure API interaction.

"},{"location":"configuration/authentication/transports/cookie/#configuration","title":"Configuration","text":"
from fastapi_users.authentication import CookieTransport\n\ncookie_transport = CookieTransport(cookie_max_age=3600)\n

As you can see, instantiation is quite simple. It accepts the following arguments:

  • cookie_name (fastapiusersauth): Name of the cookie.
  • cookie_max_age (Optional[int]): The lifetime of the cookie in seconds. None by default, which means it's a session cookie.
  • cookie_path (/): Cookie path.
  • cookie_domain (None): Cookie domain.
  • cookie_secure (True): Whether to only send the cookie to the server via SSL request.
  • cookie_httponly (True): Whether to prevent access to the cookie via JavaScript.
  • cookie_samesite (lax): A string that specifies the samesite strategy for the cookie. Valid values are lax, strict and none. Defaults to lax.
"},{"location":"configuration/authentication/transports/cookie/#login","title":"Login","text":"

This method will return a response with a valid set-cookie header upon successful login:

204 No content

Check documentation about login route.

"},{"location":"configuration/authentication/transports/cookie/#logout","title":"Logout","text":"

This method will remove the authentication cookie:

204 No content

Check documentation about logout route.

"},{"location":"configuration/authentication/transports/cookie/#authentication","title":"Authentication","text":"

This method expects that you provide a valid cookie in the headers.

"},{"location":"configuration/databases/beanie/","title":"Beanie","text":"

FastAPI Users provides the necessary tools to work with MongoDB databases using the Beanie ODM.

"},{"location":"configuration/databases/beanie/#setup-database-connection-and-collection","title":"Setup database connection and collection","text":"

The first thing to do is to create a MongoDB connection using mongodb/motor (automatically installed with Beanie).

import motor.motor_asyncio\nfrom beanie import Document\nfrom fastapi_users.db import BeanieBaseUser, BeanieUserDatabase\n\nDATABASE_URL = \"mongodb://localhost:27017\"\nclient = motor.motor_asyncio.AsyncIOMotorClient(\n    DATABASE_URL, uuidRepresentation=\"standard\"\n)\ndb = client[\"database_name\"]\n\n\nclass User(BeanieBaseUser, Document):\n    pass\n\n\nasync def get_user_db():\n    yield BeanieUserDatabase(User)\n

You can choose any name for the database.

"},{"location":"configuration/databases/beanie/#create-the-user-model","title":"Create the User model","text":"

As for any Beanie ODM model, we'll create a User model.

import motor.motor_asyncio\nfrom beanie import Document\nfrom fastapi_users.db import BeanieBaseUser, BeanieUserDatabase\n\nDATABASE_URL = \"mongodb://localhost:27017\"\nclient = motor.motor_asyncio.AsyncIOMotorClient(\n    DATABASE_URL, uuidRepresentation=\"standard\"\n)\ndb = client[\"database_name\"]\n\n\nclass User(BeanieBaseUser, Document):\n    pass\n\n\nasync def get_user_db():\n    yield BeanieUserDatabase(User)\n

As you can see, FastAPI Users provides a base class that will include base fields for our User table. You can of course add you own fields there to fit to your needs!

Info

The base class is configured to automatically create a unique index on id and email.

Info

If you want to add your own custom settings to your User document model - like changing the collection name - don't forget to let your inner Settings class inherit the pre-defined settings from BeanieBaseUser like this: class Settings(BeanieBaseUser.Settings): # ...! See Beanie's documentation on Settings for details.

"},{"location":"configuration/databases/beanie/#create-the-database-adapter","title":"Create the database adapter","text":"

The database adapter of FastAPI Users makes the link between your database configuration and the users logic. It should be generated by a FastAPI dependency.

import motor.motor_asyncio\nfrom beanie import Document\nfrom fastapi_users.db import BeanieBaseUser, BeanieUserDatabase\n\nDATABASE_URL = \"mongodb://localhost:27017\"\nclient = motor.motor_asyncio.AsyncIOMotorClient(\n    DATABASE_URL, uuidRepresentation=\"standard\"\n)\ndb = client[\"database_name\"]\n\n\nclass User(BeanieBaseUser, Document):\n    pass\n\n\nasync def get_user_db():\n    yield BeanieUserDatabase(User)\n

Notice that we pass a reference to the User model we defined above.

"},{"location":"configuration/databases/beanie/#initialize-beanie","title":"Initialize Beanie","text":"

When initializing your FastAPI app, it's important that you initialize Beanie so it can discover your models. We can achieve this using Lifespan Events on the FastAPI app:

from contextlib import asynccontextmanager\nfrom beanie import init_beanie\n\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    await init_beanie(\n        database=db,  # (1)!\n        document_models=[\n            User,  # (2)!\n        ],\n    )\n    yield\n\napp = FastAPI(lifespan=lifespan)\n
  1. This is the db Motor database instance we defined above.

  2. This is the Beanie User model we defined above. Don't forget to also add your very own models!

"},{"location":"configuration/databases/sqlalchemy/","title":"SQLAlchemy","text":"

FastAPI Users provides the necessary tools to work with SQL databases thanks to SQLAlchemy ORM with asyncio.

"},{"location":"configuration/databases/sqlalchemy/#asynchronous-driver","title":"Asynchronous driver","text":"

To work with your DBMS, you'll need to install the corresponding asyncio driver. The common choices are:

  • For PostgreSQL: pip install asyncpg
  • For SQLite: pip install aiosqlite

Examples of DB_URLs are:

  • PostgreSQL: engine = create_engine('postgresql+asyncpg://user:password@host:port/name')
  • SQLite: engine = create_engine('sqlite+aiosqlite:///name.db')

For the sake of this tutorial from now on, we'll use a simple SQLite database.

Warning

When using asynchronous sessions, ensure Session.expire_on_commit is set to False as recommended by the SQLAlchemy docs on asyncio. The examples on this documentation already have this setting correctly defined to False when using the async_sessionmaker factory.

"},{"location":"configuration/databases/sqlalchemy/#create-the-user-model","title":"Create the User model","text":"

As for any SQLAlchemy ORM model, we'll create a User model.

from typing import AsyncGenerator\n\nfrom fastapi import Depends\nfrom fastapi_users.db import SQLAlchemyBaseUserTableUUID, SQLAlchemyUserDatabase\nfrom sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine\nfrom sqlalchemy.orm import DeclarativeBase\n\nDATABASE_URL = \"sqlite+aiosqlite:///./test.db\"\n\n\nclass Base(DeclarativeBase):\n    pass\n\n\nclass User(SQLAlchemyBaseUserTableUUID, Base):\n    pass\n\n\nengine = create_async_engine(DATABASE_URL)\nasync_session_maker = async_sessionmaker(engine, expire_on_commit=False)\n\n\nasync def create_db_and_tables():\n    async with engine.begin() as conn:\n        await conn.run_sync(Base.metadata.create_all)\n\n\nasync def get_async_session() -> AsyncGenerator[AsyncSession, None]:\n    async with async_session_maker() as session:\n        yield session\n\n\nasync def get_user_db(session: AsyncSession = Depends(get_async_session)):\n    yield SQLAlchemyUserDatabase(session, User)\n

As you can see, FastAPI Users provides a base class that will include base fields for our User table. You can of course add you own fields there to fit to your needs!

Primary key is defined as UUID

By default, we use UUID as a primary key ID for your user. If you want to use another type, like an auto-incremented integer, you can use SQLAlchemyBaseUserTable as base class and define your own id column.

class User(SQLAlchemyBaseUserTable[int], Base):\n    id: Mapped[int] = mapped_column(Integer, primary_key=True)\n

Notice that SQLAlchemyBaseUserTable expects a generic type to define the actual type of ID you use.

"},{"location":"configuration/databases/sqlalchemy/#implement-a-function-to-create-the-tables","title":"Implement a function to create the tables","text":"

We'll now create an utility function to create all the defined tables.

from typing import AsyncGenerator\n\nfrom fastapi import Depends\nfrom fastapi_users.db import SQLAlchemyBaseUserTableUUID, SQLAlchemyUserDatabase\nfrom sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine\nfrom sqlalchemy.orm import DeclarativeBase\n\nDATABASE_URL = \"sqlite+aiosqlite:///./test.db\"\n\n\nclass Base(DeclarativeBase):\n    pass\n\n\nclass User(SQLAlchemyBaseUserTableUUID, Base):\n    pass\n\n\nengine = create_async_engine(DATABASE_URL)\nasync_session_maker = async_sessionmaker(engine, expire_on_commit=False)\n\n\nasync def create_db_and_tables():\n    async with engine.begin() as conn:\n        await conn.run_sync(Base.metadata.create_all)\n\n\nasync def get_async_session() -> AsyncGenerator[AsyncSession, None]:\n    async with async_session_maker() as session:\n        yield session\n\n\nasync def get_user_db(session: AsyncSession = Depends(get_async_session)):\n    yield SQLAlchemyUserDatabase(session, User)\n

This function can be called, for example, during the initialization of your FastAPI app.

Warning

In production, it's strongly recommended to setup a migration system to update your SQL schemas. See Alembic.

"},{"location":"configuration/databases/sqlalchemy/#create-the-database-adapter-dependency","title":"Create the database adapter dependency","text":"

The database adapter of FastAPI Users makes the link between your database configuration and the users logic. It should be generated by a FastAPI dependency.

from typing import AsyncGenerator\n\nfrom fastapi import Depends\nfrom fastapi_users.db import SQLAlchemyBaseUserTableUUID, SQLAlchemyUserDatabase\nfrom sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine\nfrom sqlalchemy.orm import DeclarativeBase\n\nDATABASE_URL = \"sqlite+aiosqlite:///./test.db\"\n\n\nclass Base(DeclarativeBase):\n    pass\n\n\nclass User(SQLAlchemyBaseUserTableUUID, Base):\n    pass\n\n\nengine = create_async_engine(DATABASE_URL)\nasync_session_maker = async_sessionmaker(engine, expire_on_commit=False)\n\n\nasync def create_db_and_tables():\n    async with engine.begin() as conn:\n        await conn.run_sync(Base.metadata.create_all)\n\n\nasync def get_async_session() -> AsyncGenerator[AsyncSession, None]:\n    async with async_session_maker() as session:\n        yield session\n\n\nasync def get_user_db(session: AsyncSession = Depends(get_async_session)):\n    yield SQLAlchemyUserDatabase(session, User)\n

Notice that we define first a get_async_session dependency returning us a fresh SQLAlchemy session to interact with the database.

It's then used inside the get_user_db dependency to generate our adapter. Notice that we pass it two things:

  • The session instance we just injected.
  • The User class, which is the actual SQLAlchemy model.
"},{"location":"configuration/routers/","title":"Routers","text":"

We're almost there! The last step is to configure the FastAPIUsers object that will wire the user manager, the authentication classes and let us generate the actual API routes.

"},{"location":"configuration/routers/#configure-fastapiusers","title":"Configure FastAPIUsers","text":"

Configure FastAPIUsers object with the elements we defined before. More precisely:

  • get_user_manager: Dependency callable getter to inject the user manager class instance. See UserManager.
  • auth_backends: List of authentication backends. See Authentication.
import uuid\n\nfrom fastapi_users import FastAPIUsers\n\nfrom .db import User\n\nfastapi_users = FastAPIUsers[User, uuid.UUID](\n    get_user_manager,\n    [auth_backend],\n)\n

Typing: User and ID generic types are expected

You can see that we define two generic types when instantiating:

  • User, which is the user model we defined in the database part
  • The ID, which should correspond to the type of ID you use on your model. Here, we chose UUID, but it can be anything, like an integer or a MongoDB ObjectID.

It'll help you to have good type-checking and auto-completion.

"},{"location":"configuration/routers/#available-routers","title":"Available routers","text":"

This helper class will let you generate useful routers to setup the authentication system. Each of them is optional, so you can pick only the one that you are interested in! Here are the routers provided:

  • Auth router: Provides /login and /logout routes for a given authentication backend.
  • Register router: Provides /register routes to allow a user to create a new account.
  • Reset password router: Provides /forgot-password and /reset-password routes to allow a user to reset its password.
  • Verify router: Provides /request-verify-token and /verify routes to manage user e-mail verification.
  • Users router: Provides routes to manage users.
  • OAuth router: Provides routes to perform an OAuth authentication against a service provider (like Google or Facebook).

You should check out each of them to understand how to use them.

"},{"location":"configuration/routers/auth/","title":"Auth router","text":"

The auth router will generate /login and /logout routes for a given authentication backend.

Check the routes usage to learn how to use them.

"},{"location":"configuration/routers/auth/#setup","title":"Setup","text":"
import uuid\n\nfrom fastapi import FastAPI\nfrom fastapi_users import FastAPIUsers\n\nfrom .db import User\n\nfastapi_users = FastAPIUsers[User, uuid.UUID](\n    get_user_manager,\n    [auth_backend],\n)\n\napp = FastAPI()\napp.include_router(\n    fastapi_users.get_auth_router(auth_backend),\n    prefix=\"/auth/jwt\",\n    tags=[\"auth\"],\n)\n
"},{"location":"configuration/routers/auth/#optional-user-verification","title":"Optional: user verification","text":"

You can require the user to be verified (i.e. is_verified property set to True) to allow login. You have to set the requires_verification parameter to True on the router instantiation method:

app.include_router(\n    fastapi_users.get_auth_router(auth_backend, requires_verification=True),\n    prefix=\"/auth/jwt\",\n    tags=[\"auth\"],\n)\n
"},{"location":"configuration/routers/register/","title":"Register routes","text":"

The register router will generate a /register route to allow a user to create a new account.

Check the routes usage to learn how to use them.

"},{"location":"configuration/routers/register/#setup","title":"Setup","text":"
import uuid\n\nfrom fastapi import FastAPI\nfrom fastapi_users import FastAPIUsers\n\nfrom .db import User\nfrom .schemas import UserCreate, UserRead\n\nfastapi_users = FastAPIUsers[User, uuid.UUID](\n    get_user_manager,\n    [auth_backend],\n)\n\napp = FastAPI()\napp.include_router(\n    fastapi_users.get_register_router(UserRead, UserCreate),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\n
"},{"location":"configuration/routers/reset/","title":"Reset password router","text":"

The reset password router will generate /forgot-password (the user asks for a token to reset its password) and /reset-password (the user changes its password given the token) routes.

Check the routes usage to learn how to use them.

"},{"location":"configuration/routers/reset/#setup","title":"Setup","text":"
import uuid\n\nfrom fastapi import FastAPI\nfrom fastapi_users import FastAPIUsers\n\nfrom .db import User\n\nfastapi_users = FastAPIUsers[User, uuid.UUID](\n    get_user_manager,\n    [auth_backend],\n)\n\napp = FastAPI()\napp.include_router(\n    fastapi_users.get_reset_password_router(),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\n
"},{"location":"configuration/routers/users/","title":"Users router","text":"

This router provides routes to manage users. Check the routes usage to learn how to use them.

"},{"location":"configuration/routers/users/#setup","title":"Setup","text":"
import uuid\n\nfrom fastapi import FastAPI\nfrom fastapi_users import FastAPIUsers\n\nfrom .db import User\nfrom .schemas import UserRead, UserUpdate\n\nfastapi_users = FastAPIUsers[User, uuid.UUID](\n    get_user_manager,\n    [auth_backend],\n)\n\napp = FastAPI()\napp.include_router(\n    fastapi_users.get_users_router(UserRead, UserUpdate),\n    prefix=\"/users\",\n    tags=[\"users\"],\n)\n
"},{"location":"configuration/routers/users/#optional-user-verification","title":"Optional: user verification","text":"

You can require the user to be verified (i.e. is_verified property set to True) to access those routes. You have to set the requires_verification parameter to True on the router instantiation method:

app.include_router(\n    fastapi_users.get_users_router(UserRead, UserUpdate, requires_verification=True),\n    prefix=\"/users\",\n    tags=[\"users\"],\n)\n
"},{"location":"configuration/routers/verify/","title":"Verify router","text":"

This router provides routes to manage user email verification. Check the routes usage to learn how to use them.

\ud83d\udc4f\ud83d\udc4f\ud83d\udc4f

A big thank you to Edd Salkield and Mark Todd who worked hard on this feature!

"},{"location":"configuration/routers/verify/#setup","title":"Setup","text":"
import uuid\n\nfrom fastapi import FastAPI\nfrom fastapi_users import FastAPIUsers\n\nfrom .db import User\nfrom .schemas import UserRead\n\nfastapi_users = FastAPIUsers[User, uuid.UUID](\n    get_user_manager,\n    [auth_backend],\n)\n\napp = FastAPI()\napp.include_router(\n    fastapi_users.get_verify_router(UserRead),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\n
"},{"location":"cookbook/create-user-programmatically/","title":"Create a user programmatically","text":"

Sometimes, you'll need to create a user programmatically in the code rather than passing by the REST API endpoint. To do this, we'll create a function that you can call from your code.

In this context, we are outside the dependency injection mechanism of FastAPI, so we have to take care of instantiating the UserManager class and all other dependent objects manually.

For this cookbook, we'll consider you are starting from the SQLAlchemy full example, but it'll be rather similar for other DBMS.

"},{"location":"cookbook/create-user-programmatically/#1-define-dependencies-as-context-managers","title":"1. Define dependencies as context managers","text":"

Generally, FastAPI dependencies are defined as generators, using the yield keyword. FastAPI knows very well to handle them inside its dependency injection system. For example, here is the definition of the get_user_manager dependency:

async def get_user_manager(user_db: SQLAlchemyUserDatabase = Depends(get_user_db)):\n  yield UserManager(user_db)\n

In Python, when we want to use a generator, we have to use a for loop, which would be a bit unnatural in this context since we have only one value to get, the user manager instance. To avoid this, we'll transform them into context managers, so we can call them using the with..as syntax. Fortunately, the standard library provides tools to automatically transform generators into context managers.

In the following sample, we import our dependencies and create a context manager version using contextlib.asynccontextmanager:

import contextlib\n\nfrom app.db import get_async_session, get_user_db\nfrom app.schemas import UserCreate\nfrom app.users import get_user_manager\nfrom fastapi_users.exceptions import UserAlreadyExists\n\nget_async_session_context = contextlib.asynccontextmanager(get_async_session)\nget_user_db_context = contextlib.asynccontextmanager(get_user_db)\nget_user_manager_context = contextlib.asynccontextmanager(get_user_manager)\n\n\nasync def create_user(email: str, password: str, is_superuser: bool = False):\n    try:\n        async with get_async_session_context() as session:\n            async with get_user_db_context(session) as user_db:\n                async with get_user_manager_context(user_db) as user_manager:\n                    user = await user_manager.create(\n                        UserCreate(\n                            email=email, password=password, is_superuser=is_superuser\n                        )\n                    )\n                    print(f\"User created {user}\")\n                    return user\n    except UserAlreadyExists:\n        print(f\"User {email} already exists\")\n        raise\n

I have other dependencies

Since FastAPI Users fully embraces dependency injection, you may have more arguments passed to your database or user manager dependencies. It's important then to not forget anyone. Once again, outside the dependency injection system, you are responsible of instantiating everything yourself.

"},{"location":"cookbook/create-user-programmatically/#2-write-a-function","title":"2. Write a function","text":"

We are now ready to write a function. The example below shows you a basic example but you can of course adapt it to your own needs. The key part here is once again to take care of opening every context managers and pass them every required arguments, as the dependency manager would do.

import contextlib\n\nfrom app.db import get_async_session, get_user_db\nfrom app.schemas import UserCreate\nfrom app.users import get_user_manager\nfrom fastapi_users.exceptions import UserAlreadyExists\n\nget_async_session_context = contextlib.asynccontextmanager(get_async_session)\nget_user_db_context = contextlib.asynccontextmanager(get_user_db)\nget_user_manager_context = contextlib.asynccontextmanager(get_user_manager)\n\n\nasync def create_user(email: str, password: str, is_superuser: bool = False):\n    try:\n        async with get_async_session_context() as session:\n            async with get_user_db_context(session) as user_db:\n                async with get_user_manager_context(user_db) as user_manager:\n                    user = await user_manager.create(\n                        UserCreate(\n                            email=email, password=password, is_superuser=is_superuser\n                        )\n                    )\n                    print(f\"User created {user}\")\n                    return user\n    except UserAlreadyExists:\n        print(f\"User {email} already exists\")\n        raise\n
"},{"location":"cookbook/create-user-programmatically/#3-use-it","title":"3. Use it","text":"

You can now easily use it in a script. For example:

import asyncio\n\nif __name__ == \"__main__\":\n  asyncio.run(create_user(\"king.arthur@camelot.bt\", \"guinevere\"))\n
"},{"location":"migration/08_to_1x/","title":"0.8.x \u27a1\ufe0f 1.x.x","text":"

1.0 version introduces major breaking changes that need you to update some of your code and migrate your data.

"},{"location":"migration/08_to_1x/#id-are-uuid","title":"Id. are UUID","text":"

Users and OAuth accounts id. are now represented as real UUID objects instead of plain strings. This change was introduced to leverage efficient storage and indexing for DBMS that supports UUID (especially PostgreSQL and Mongo).

"},{"location":"migration/08_to_1x/#in-python-code","title":"In Python code","text":"

If you were doing comparison betwen a user id. and a string (in unit tests for example), you should now cast the id. to string:

# Before\nassert \"d35d213e-f3d8-4f08-954a-7e0d1bea286f\" == user.id\n\n# Now\nassert \"d35d213e-f3d8-4f08-954a-7e0d1bea286f\" == str(user.id)\n

If you were refering to user id. in your Pydantic models, the field should now be of UUID4 type instead of str:

from pydantic import BaseModel, UUID4\n\n# Before\nclass Model(BaseModel):\n    user_id: str\n\n# After\nclass Model(BaseModel):\n    user_id: UUID4\n
"},{"location":"migration/08_to_1x/#mongodb","title":"MongoDB","text":"

To avoid any issues, it's recommended to use the standard UUID representation when instantiating the MongoDB client:

DATABASE_URL = \"mongodb://localhost:27017\"\nclient = motor.motor_asyncio.AsyncIOMotorClient(\n    DATABASE_URL, uuidRepresentation=\"standard\"\n)\n

This parameter controls how the UUID values will be encoded in the database. By default, it's set to pythonLegacy but new applications should consider setting this to standard for cross language compatibility. Read more about this.

"},{"location":"migration/08_to_1x/#in-database","title":"In database","text":"

Id. were before stored as strings in the database. You should make a migration to convert string data to UUID data.

Danger

Scripts below are provided as guidelines. Please review them carefully, adapt them and check that they are working on a test database before applying them to production. BE CAREFUL. THEY CAN DESTROY YOUR DATA..

"},{"location":"migration/08_to_1x/#postgresql","title":"PostgreSQL","text":"

PostgreSQL supports UUID type. If not already, you should enable the uuid-ossp extension:

CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";\n

To convert the existing id. string column, we can:

  1. Create a new column with UUID type.
  2. Fill it with the id. converted to UUID.
  3. Drop the original id. column.
  4. Make the new column a primary key and rename it.
ALTER TABLE \"user\" ADD uuid_id UUID;\nUPDATE \"user\" SET uuid_id = uuid(id);\nALTER TABLE \"user\" DROP id;\nALTER TABLE \"user\" ADD PRIMARY KEY (uuid_id);\nALTER TABLE \"user\" RENAME COLUMN uuid_id TO id;\n
"},{"location":"migration/08_to_1x/#mysql","title":"MySQL","text":"

MySQL doesn't support UUID type. We'll just convert the column to CHAR(36) type:

ALTER TABLE \"user\" MODIFY id CHAR(36);\n
"},{"location":"migration/08_to_1x/#mongodb_1","title":"MongoDB","text":""},{"location":"migration/08_to_1x/#mongo-shell","title":"Mongo shell","text":"

For MongoDB, we can use a forEach iterator to convert the id. for each document:

db.getCollection('users').find().forEach(function(user) {\n  var uuid = UUID(user.id);\n  db.getCollection('users').update({_id: user._id}, [{$set: {id: uuid}}]);\n});\n
"},{"location":"migration/08_to_1x/#python","title":"Python","text":"
import uuid\n\nimport motor.motor_asyncio\n\n\nasync def migrate_uuid():\n    client = motor.motor_asyncio.AsyncIOMotorClient(\n        DATABASE_URL, uuidRepresentation=\"standard\"\n    )\n    db = client[\"database_name\"]\n    users = db[\"users\"]\n\n    async for user in users.find({}):\n        await users.update_one(\n            {\"_id\": user[\"_id\"]},\n            {\"$set\": {\"id\": uuid.UUID(user[\"id\"])}},\n        )\n
"},{"location":"migration/08_to_1x/#splitted-routers","title":"Splitted routers","text":"

You now have the responsibility to wire the routers. FastAPI Users doesn't give a bloated users router anymore.

Event handlers are also removed. You have to provide your \"after-\" logic as a parameter of the router generator.

"},{"location":"migration/08_to_1x/#before","title":"Before","text":"
jwt_authentication = JWTAuthentication(secret=SECRET, lifetime_seconds=3600)\n\napp = FastAPI()\nfastapi_users = FastAPIUsers(\n    user_db, [jwt_authentication], User, UserCreate, UserUpdate, UserDB,\n)\napp.include_router(fastapi_users.router, prefix=\"/users\", tags=[\"users\"])\n\n\n@fastapi_users.on_after_register()\ndef on_after_register(user: User, request: Request):\n    print(f\"User {user.id} has registered.\")\n\n\n@fastapi_users.on_after_forgot_password()\ndef on_after_forgot_password(user: User, token: str, request: Request):\n    print(f\"User {user.id} has forgot their password. Reset token: {token}\")\n
"},{"location":"migration/08_to_1x/#after","title":"After","text":"
def on_after_register(user: UserDB, request: Request):\n    print(f\"User {user.id} has registered.\")\n\n\ndef on_after_forgot_password(user: UserDB, token: str, request: Request):\n    print(f\"User {user.id} has forgot their password. Reset token: {token}\")\n\n\njwt_authentication = JWTAuthentication(secret=SECRET, lifetime_seconds=3600)\n\napp = FastAPI()\nfastapi_users = FastAPIUsers(\n    user_db, [jwt_authentication], User, UserCreate, UserUpdate, UserDB,\n)\napp.include_router(\n    fastapi_users.get_auth_router(jwt_authentication), prefix=\"/auth/jwt\", tags=[\"auth\"]\n)\napp.include_router(\n    fastapi_users.get_register_router(on_after_register), prefix=\"/auth\", tags=[\"auth\"]\n)\napp.include_router(\n    fastapi_users.get_reset_password_router(\n        SECRET, after_forgot_password=on_after_forgot_password\n    ),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\napp.include_router(fastapi_users.get_users_router(), prefix=\"/users\", tags=[\"users\"])\n

Important things to notice:

  • FastAPIUsers takes two arguments less (reset_password_token_secret and reset_password_token_lifetime_seconds).
  • You have more flexibility to choose the prefix and tags of the routers.
  • The /login//logout are now your responsibility to include for each backend. The path will change (before /login/jwt, after /jwt/login).
  • If you don't care about some of those routers, you can discard them.
"},{"location":"migration/1x_to_2x/","title":"1.x.x \u27a1\ufe0f 2.x.x","text":""},{"location":"migration/1x_to_2x/#jwt-authentication-backend","title":"JWT authentication backend","text":"

To be fully compatible with Swagger authentication, the output of a successful login operation with the JWT authentication backend has changed:

Before

{\n    \"token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiOTIyMWZmYzktNjQwZi00MzcyLTg2ZDMtY2U2NDJjYmE1NjAzIiwiYXVkIjoiZmFzdGFwaS11c2VyczphdXRoIiwiZXhwIjoxNTcxNTA0MTkzfQ.M10bjOe45I5Ncu_uXvOmVV8QxnL-nZfcH96U90JaocI\"\n}\n

After

{\n    \"access_token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiOTIyMWZmYzktNjQwZi00MzcyLTg2ZDMtY2U2NDJjYmE1NjAzIiwiYXVkIjoiZmFzdGFwaS11c2VyczphdXRoIiwiZXhwIjoxNTcxNTA0MTkzfQ.M10bjOe45I5Ncu_uXvOmVV8QxnL-nZfcH96U90JaocI\",\n    \"token_type\": \"bearer\"\n}\n

Make sure to update your clients to read the token in the right property.

"},{"location":"migration/2x_to_3x/","title":"2.x.x \u27a1\ufe0f 3.x.x","text":""},{"location":"migration/2x_to_3x/#emails-are-now-case-insensitive","title":"Emails are now case-insensitive","text":"

Before 3.x.x, the local part (before the @) of the email address was case-sensitive. Therefore, king.arthur@camelot.bt and King.Arthur@camelot.bt were considered as two different users. This behaviour was a bit confusing and not consistent with 99% of web services out there.

After 3.x.x, users are fetched from the database with a case-insensitive email search. Bear in mind though that if the user registers with the email King.Arthur@camelot.bt, it will be stored exactly like this in the database (with casing) ; but he will be able to login as king.arthur@camelot.bt.

Danger

It's super important then, before you upgrade to 3.x.x that you check if there are several users with the same email with different cases ; and that you merge or delete those accounts.

"},{"location":"migration/3x_to_4x/","title":"3.x.x \u27a1\ufe0f 4.x.x","text":""},{"location":"migration/3x_to_4x/#expires_at-property-in-oauthaccount-is-now-optional","title":"expires_at property in OAuthAccount is now optional","text":"

Before 4.x.x, the expires_at property in OAuthAccount model was mandatory. It was causing issues with some services that don't have such expiration property.

If you use SQLAlchemy or Tortoise databases adapters, you'll have to make a migration to update your database schema.

"},{"location":"migration/4x_to_5x/","title":"4.x.x \u27a1\ufe0f 5.x.x","text":""},{"location":"migration/4x_to_5x/#new-property-is_verified-in-user-model","title":"New property is_verified in User model.","text":"

Starting 5.x.x., there is a new e-mail verification feature. Even if optional, the is_verified property has been added to the User model.

If you use SQLAlchemy or Tortoise databases adapters, you'll have to make a migration to update your database schema.

"},{"location":"migration/6x_to_7x/","title":"6.x.x \u27a1\ufe0f 7.x.x","text":"
  • The deprecated dependencies to retrieve current user have been removed. Use the current_user factory instead. [Documentation]
  • When trying to authenticate a not verified user, a status code 403 is raised instead of status code 401. Thanks @daanbeverdam \ud83c\udf89 [Documentation]
  • Your UserUpdate model shouldn't inherit from the base User class. If you have custom fields, you should repeat them in this model. [Documentation]
  • Database adapters now live in their own repositories and packages.
  • When upgrading to v7.0.0, the dependency for your database adapter should automatically be installed.
  • The import statements remain unchanged.
"},{"location":"migration/7x_to_8x/","title":"7.x.x \u27a1\ufe0f 8.x.x","text":"

Version 8 includes the biggest code changes since version 1. We reorganized lot of parts of the code to make it even more modular and integrate more into the dependency injection system of FastAPI.

Most importantly, you need now to implement a UserManager class and a associated dependency to create an instance of this class.

"},{"location":"migration/7x_to_8x/#event-handlers-should-live-in-the-usermanager","title":"Event handlers should live in the UserManager","text":"

Before, event handlers like on_after_register or on_after_forgot_password were defined in their own functions that were passed as arguments of router generators.

Now, they should be methods of the UserManager class.

You can read more in the UserManager documentation.

"},{"location":"migration/7x_to_8x/#password-validation-should-live-in-the-usermanager","title":"Password validation should live in the UserManager","text":"

Before, password validation was defined in its own function that was passed as argument of FastAPIUsers.

Now, it should be a method of the UserManager class.

You can read more in the UserManager documentation.

"},{"location":"migration/7x_to_8x/#verify-token-secret-and-lifetime-parameters-are-attributes-of-usermanager","title":"Verify token secret and lifetime parameters are attributes of UserManager","text":"

Before, verify token and lifetime parameters were passed as argument of get_verify_router.

Now, they should be defined as attributes of the UserManager class.

You can read more in the UserManager documentation.

"},{"location":"migration/7x_to_8x/#reset-password-token-secret-and-lifetime-parameters-are-attributes-of-usermanager","title":"Reset password token secret and lifetime parameters are attributes of UserManager","text":"

Before, reset password token and lifetime parameters were passed as argument of get_verify_router.

Now, they should be defined as attributes of the UserManager class.

You can read more in the UserManager documentation.

"},{"location":"migration/7x_to_8x/#database-adapter-should-be-provided-in-a-dependency","title":"Database adapter should be provided in a dependency","text":"

Before, we advised to directly instantiate the database adapter class.

Now, it should be instantiated inside a dependency that you define yourself. The benefit of this is that it lives in the dependency injection system of FastAPI, allowing you to have more dynamic logic to create your instance.

\u27a1\ufe0f I'm using SQLAlchemy

\u27a1\ufe0f I'm using MongoDB

\u27a1\ufe0f I'm using Tortoise ORM

\u27a1\ufe0f I'm using ormar

"},{"location":"migration/7x_to_8x/#fastapiusers-now-expect-a-get_user_manager-dependency","title":"FastAPIUsers now expect a get_user_manager dependency","text":"

Before, the database adapter instance was passed as argument of FastAPIUsers.

Now, you should define a get_user_manager dependency returning an instance of your UserManager class. This dependency will be dependent of the database adapter dependency.

You can read more in the UserManager documentation and FastAPIUsers documentation

"},{"location":"migration/7x_to_8x/#lost","title":"Lost?","text":"

If you're unsure or a bit lost, make sure to check the full working examples.

"},{"location":"migration/8x_to_9x/","title":"8.x.x \u27a1\ufe0f 9.x.x","text":"

Version 9 revamps the authentication backends: we splitted the logic of a backend into two: the transport, which is how the token will be carried over the request and the strategy, which is how the token is generated and secured.

The benefit of this is that we'll soon be able to propose new strategies, like database session tokens, without having to repeat the transport logic which remains the same.

"},{"location":"migration/8x_to_9x/#convert-the-authentication-backend","title":"Convert the authentication backend","text":"

You now have to generate an authentication backend with a transport and a strategy.

"},{"location":"migration/8x_to_9x/#i-used-jwtauthentication","title":"I used JWTAuthentication","text":"BeforeAfter
from fastapi_users.authentication import JWTAuthentication\n\njwt_authentication = JWTAuthentication(\n    secret=SECRET, lifetime_seconds=3600, tokenUrl=\"auth/jwt/login\"\n)\n
from fastapi_users.authentication import AuthenticationBackend, BearerTransport, JWTStrategy\n\nSECRET = \"SECRET\"\n\nbearer_transport = BearerTransport(tokenUrl=\"auth/jwt/login\")\n\ndef get_jwt_strategy() -> JWTStrategy:\n    return JWTStrategy(secret=SECRET, lifetime_seconds=3600)\n\nauth_backend = AuthenticationBackend(\n    name=\"jwt\",\n    transport=bearer_transport,\n    get_strategy=get_jwt_strategy,\n)\n

Warning

There is no default name anymore: you need to provide it yourself for each of your backends.

"},{"location":"migration/8x_to_9x/#i-used-cookieauthentication","title":"I used CookieAuthentication","text":"BeforeAfter
from fastapi_users.authentication import CookieAuthentication\n\ncookie_authentication = CookieAuthentication(secret=SECRET, lifetime_seconds=3600)\n
from fastapi_users.authentication import AuthenticationBackend, CookieTransport, JWTStrategy\n\nSECRET = \"SECRET\"\n\ncookie_transport = CookieTransport(cookie_max_age=3600)\n\ndef get_jwt_strategy() -> JWTStrategy:\n    return JWTStrategy(secret=SECRET, lifetime_seconds=3600)\n\nauth_backend = AuthenticationBackend(\n    name=\"cookie\",\n    transport=cookie_transport,\n    get_strategy=get_jwt_strategy,\n)\n

Warning

There is no default name anymore: you need to provide it yourself for each of your backends.

Tip

Notice that the strategy is the same for both authentication backends. That's the beauty of this approach: the token generation is decoupled from its transport.

"},{"location":"migration/8x_to_9x/#oauth-one-router-for-each-backend","title":"OAuth: one router for each backend","text":"

Before, a single OAuth router was enough to login with any of your authentication backend. Now, you need to generate a router for each of your backends.

BeforeAfter
app.include_router(\n    fastapi_users.get_oauth_router(google_oauth_client, \"SECRET\"),\n    prefix=\"/auth/google\",\n    tags=[\"auth\"],\n)\n
app.include_router(\n    fastapi_users.get_oauth_router(google_oauth_client, auth_backend, \"SECRET\"),\n    prefix=\"/auth/google\",\n    tags=[\"auth\"],\n)\n
"},{"location":"migration/8x_to_9x/#authentication_backend-is-not-needed-on-authorize","title":"authentication_backend is not needed on /authorize","text":"

The consequence of this is that you don't need to specify the authentication backend when making a request to /authorize.

BeforeAfter
curl \\\n-H \"Content-Type: application/json\" \\\n-X GET \\\nhttp://localhost:8000/auth/google/authorize?authentication_backend=jwt\n
curl \\\n-H \"Content-Type: application/json\" \\\n-X GET \\\nhttp://localhost:8000/auth/google/authorize\n
"},{"location":"migration/8x_to_9x/#lost","title":"Lost?","text":"

If you're unsure or a bit lost, make sure to check the full working examples.

"},{"location":"migration/9x_to_10x/","title":"9.x.x \u27a1\ufe0f 10.x.x","text":"

Version 10 marks important changes in how we manage User models and their ID.

Before, we were relying only on Pydantic models to work with users. In particular the current_user dependency would return you an instance of UserDB, a Pydantic model. This proved to be quite problematic with some ORM if you ever needed to retrieve relationship data or make specific requests.

Now, FastAPI Users is designed to always return you a native object for your ORM model, whether it's an SQLAlchemy model or a Beanie document. Pydantic models are now only used for validation and serialization inside the API.

Before, we were forcing the use of UUID as primary key ID; a consequence of the design above. This proved to be quite problematic on some databases, like MongoDB which uses a special ObjectID format by default. Some SQL folks also prefer to use traditional auto-increment integers.

Now, FastAPI Users is designed to use generic ID type. It means that you can use any type you want for your user's ID. By default, SQLAlchemy adapter still use UUID; but you can quite easily switch to another thing, like an integer. Beanie adapter for MongoDB will use native ObjectID by default, but it also can be overriden.

As you may have guessed, those changes imply quite a lot of breaking changes.

"},{"location":"migration/9x_to_10x/#user-models-and-database-adapter","title":"User models and database adapter","text":""},{"location":"migration/9x_to_10x/#sqlalchemy-orm","title":"SQLAlchemy ORM","text":"

We've removed the old SQLAlchemy dependency support, so the dependency is now fastapi-users[sqlalchemy].

BeforeAfter
fastapi\nfastapi-users[sqlalchemy2]\nuvicorn[standard]\naiosqlite\n
fastapi\nfastapi-users[sqlalchemy]\nuvicorn[standard]\naiosqlite\n

The User model base class for SQLAlchemy slightly changed to support UUID by default.

We changed the name of the class from UserTable to User: it's not a compulsory change, but since there is no risk of confusion with Pydantic models anymore, it's probably a more idiomatic naming.

BeforeAfter
class UserTable(Base, SQLAlchemyBaseUserTable):\n    pass\n
class User(SQLAlchemyBaseUserTableUUID, Base):\n    pass\n

Instantiating the SQLAlchemyUserDatabase adapter now only expects this User model. UserDB is removed.

BeforeAfter
async def get_user_db(session: AsyncSession = Depends(get_async_session)):\n    yield SQLAlchemyUserDatabase(UserDB, session, UserTable)\n
async def get_user_db(session: AsyncSession = Depends(get_async_session)):\n    yield SQLAlchemyUserDatabase(session, User)\n
"},{"location":"migration/9x_to_10x/#mongodb","title":"MongoDB","text":"

MongoDB support is now only provided through Beanie ODM. Even if you don't use it for the rest of your project, it's a very light addition that shouldn't interfere much.

BeforeAfter
fastapi\nfastapi-users[mongodb]\nuvicorn[standard]\naiosqlite\n
fastapi\nfastapi-users[beanie]\nuvicorn[standard]\naiosqlite\n

You now need to define a proper User model using Beanie.

BeforeAfter
import os\n\nimport motor.motor_asyncio\nfrom fastapi_users.db import MongoDBUserDatabase\n\nfrom app.models import UserDB\n\nDATABASE_URL = os.environ[\"DATABASE_URL\"]\nclient = motor.motor_asyncio.AsyncIOMotorClient(\n    DATABASE_URL, uuidRepresentation=\"standard\"\n)\ndb = client[\"database_name\"]\ncollection = db[\"users\"]\n\n\nasync def get_user_db():\n    yield MongoDBUserDatabase(UserDB, collection)\n
import motor.motor_asyncio\nfrom beanie import PydanticObjectId\nfrom fastapi_users.db import BeanieBaseUser, BeanieUserDatabase\n\nDATABASE_URL = \"mongodb://localhost:27017\"\nclient = motor.motor_asyncio.AsyncIOMotorClient(\n    DATABASE_URL, uuidRepresentation=\"standard\"\n)\ndb = client[\"database_name\"]\n\n\nclass User(BeanieBaseUser[PydanticObjectId]):\n    pass\n\n\nasync def get_user_db():\n    yield BeanieUserDatabase(User)\n

ID are now ObjectID by default

By default, User ID will now be native MongoDB ObjectID. If you don't want to make the transition and keep UUID you can do so by overriding the id field:

import uuid\n\nfrom pydantic import Field\n\n\nclass User(BeanieBaseUser[uuid.UUID]):\n    id: uuid.UUID = Field(default_factory=uuid.uuid4)\n

Beanie also needs to be initialized in a startup event handler of your FastAPI app:

from beanie import init_beanie\n\n\n@app.on_event(\"startup\")\nasync def on_startup():\n    await init_beanie(\n        database=db,\n        document_models=[\n            User,\n        ],\n    )\n
"},{"location":"migration/9x_to_10x/#tortoise-orm-and-ormar","title":"Tortoise ORM and ormar","text":"

Unfortunately, we sometimes need to make difficult choices to keep things sustainable. That's why we decided to not support Tortoise ORM and ormar anymore. It appeared they were not widely used.

You can still add support for those ORM yourself by implementing the necessary adapter. You can take inspiration from the SQLAlchemy one.

"},{"location":"migration/9x_to_10x/#usermanager","title":"UserManager","text":"

There is some slight changes on the UserManager class. In particular, it now needs a parse_id method that can be provided through built-in mixins.

Generic typing now expects your native User model class and the type of ID.

The user_db_model class property is removed.

BeforeAfter
class UserManager(BaseUserManager[UserCreate, UserDB]):\n    user_db_model = UserDB\n    reset_password_token_secret = SECRET\n    verification_token_secret = SECRET\n\n    async def on_after_register(self, user: UserDB, request: Optional[Request] = None):\n        print(f\"User {user.id} has registered.\")\n\n    async def on_after_forgot_password(\n        self, user: UserDB, token: str, request: Optional[Request] = None\n    ):\n        print(f\"User {user.id} has forgot their password. Reset token: {token}\")\n\n    async def on_after_request_verify(\n        self, user: UserDB, token: str, request: Optional[Request] = None\n    ):\n        print(f\"Verification requested for user {user.id}. Verification token: {token}\")\n
class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):\n    reset_password_token_secret = SECRET\n    verification_token_secret = SECRET\n\n    async def on_after_register(self, user: User, request: Optional[Request] = None):\n        print(f\"User {user.id} has registered.\")\n\n    async def on_after_forgot_password(\n        self, user: User, token: str, request: Optional[Request] = None\n    ):\n        print(f\"User {user.id} has forgot their password. Reset token: {token}\")\n\n    async def on_after_request_verify(\n        self, user: User, token: str, request: Optional[Request] = None\n    ):\n        print(f\"Verification requested for user {user.id}. Verification token: {token}\")\n

If you need to support other types of ID, you can read more about it in the dedicated section.

"},{"location":"migration/9x_to_10x/#pydantic-models","title":"Pydantic models","text":"

To better distinguish them from the ORM models, Pydantic models are now called schemas.

UserDB has been removed in favor of native models.

We changed the name of User to UserRead: it's not a compulsory change, but since there is a risk of confusion with the native model, it's highly recommended.

Besides, the BaseUser schema now accepts a generic type to specify the type of ID you use.

BeforeAfter
from fastapi_users import models\n\n\nclass User(models.BaseUser):\n    pass\n\n\nclass UserCreate(models.BaseUserCreate):\n    pass\n\n\nclass UserUpdate(models.BaseUserUpdate):\n    pass\n\n\nclass UserDB(User, models.BaseUserDB):\n    pass\n
import uuid\n\nfrom fastapi_users import schemas\n\n\nclass UserRead(schemas.BaseUser[uuid.UUID]):\n    pass\n\n\nclass UserCreate(schemas.BaseUserCreate):\n    pass\n\n\nclass UserUpdate(schemas.BaseUserUpdate):\n    pass\n
"},{"location":"migration/9x_to_10x/#fastapi-users-and-routers","title":"FastAPI Users and routers","text":"

Pydantic schemas are now way less important in this new design. As such, you don't need to pass them when initializing the FastAPIUsers class:

BeforeAfter
fastapi_users = FastAPIUsers(\n    get_user_manager,\n    [auth_backend],\n    User,\n    UserCreate,\n    UserUpdate,\n    UserDB,\n)\n
fastapi_users = FastAPIUsers[User, uuid.UUID](\n    get_user_manager,\n    [auth_backend],\n)\n

As a consequence, those schemas need to be passed when initializing the router that needs them: get_register_router, get_verify_router and get_users_router.

BeforeAfter
app.include_router(\n    fastapi_users.get_auth_router(auth_backend), prefix=\"/auth/jwt\", tags=[\"auth\"]\n)\napp.include_router(fastapi_users.get_register_router(), prefix=\"/auth\", tags=[\"auth\"])\napp.include_router(\n    fastapi_users.get_reset_password_router(),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\napp.include_router(\n    fastapi_users.get_verify_router(),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\napp.include_router(fastapi_users.get_users_router(), prefix=\"/users\", tags=[\"users\"])\n
app.include_router(\n    fastapi_users.get_auth_router(auth_backend), prefix=\"/auth/jwt\", tags=[\"auth\"]\n)\napp.include_router(\n    fastapi_users.get_register_router(UserRead, UserCreate),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\napp.include_router(\n    fastapi_users.get_reset_password_router(),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\napp.include_router(\n    fastapi_users.get_verify_router(UserRead),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\napp.include_router(\n    fastapi_users.get_users_router(UserRead, UserUpdate),\n    prefix=\"/users\",\n    tags=[\"users\"],\n)\n
"},{"location":"migration/9x_to_10x/#lost","title":"Lost?","text":"

If you're unsure or a bit lost, make sure to check the full working examples.

"},{"location":"usage/current-user/","title":"Get current user","text":"

FastAPI Users provides a dependency callable to easily inject authenticated user in your routes. They are available from your FastAPIUsers instance.

Tip

For more information about how to make an authenticated request to your API, check the documentation of your Authentication method.

"},{"location":"usage/current-user/#current_user","title":"current_user","text":"

Return a dependency callable to retrieve currently authenticated user, passing the following parameters:

  • optional: If True, None is returned if there is no authenticated user or if it doesn't pass the other requirements. Otherwise, throw 401 Unauthorized. Defaults to False.
  • active: If True, throw 401 Unauthorized if the authenticated user is inactive. Defaults to False.
  • verified: If True, throw 403 Forbidden if the authenticated user is not verified. Defaults to False.
  • superuser: If True, throw 403 Forbidden if the authenticated user is not a superuser. Defaults to False.
  • get_enabled_backends: Optional dependency callable returning a list of enabled authentication backends. Useful if you want to dynamically enable some authentication backends based on external logic, like a configuration in database. By default, all specified authentication backends are enabled. Please not however that every backends will appear in the OpenAPI documentation, as FastAPI resolves it statically.

Create it once and reuse it

This function is a factory, a function returning another function \ud83e\udd2f

It's this returned function that will be the dependency called by FastAPI in your API routes.

To avoid having to generate it on each route and avoid issues when unit testing, it's strongly recommended that you assign the result in a variable and reuse it at will in your routes. The examples below demonstrate this pattern.

"},{"location":"usage/current-user/#examples","title":"Examples","text":""},{"location":"usage/current-user/#get-the-current-user-active-or-not","title":"Get the current user (active or not)","text":"
current_user = fastapi_users.current_user()\n\n@app.get(\"/protected-route\")\ndef protected_route(user: User = Depends(current_user)):\n    return f\"Hello, {user.email}\"\n
"},{"location":"usage/current-user/#get-the-current-active-user","title":"Get the current active user","text":"
current_active_user = fastapi_users.current_user(active=True)\n\n@app.get(\"/protected-route\")\ndef protected_route(user: User = Depends(current_active_user)):\n    return f\"Hello, {user.email}\"\n
"},{"location":"usage/current-user/#get-the-current-active-and-verified-user","title":"Get the current active and verified user","text":"
current_active_verified_user = fastapi_users.current_user(active=True, verified=True)\n\n@app.get(\"/protected-route\")\ndef protected_route(user: User = Depends(current_active_verified_user)):\n    return f\"Hello, {user.email}\"\n
"},{"location":"usage/current-user/#get-the-current-active-superuser","title":"Get the current active superuser","text":"
current_superuser = fastapi_users.current_user(active=True, superuser=True)\n\n@app.get(\"/protected-route\")\ndef protected_route(user: User = Depends(current_superuser)):\n    return f\"Hello, {user.email}\"\n
"},{"location":"usage/current-user/#dynamically-enable-authentication-backends","title":"Dynamically enable authentication backends","text":"

Warning

This is an advanced feature for cases where you have several authentication backends that are enabled conditionally. In most cases, you won't need this option.

from fastapi import Request\nfrom fastapi_users.authentication import AuthenticationBackend, BearerTransport, CookieTransport, JWTStrategy\n\nSECRET = \"SECRET\"\n\nbearer_transport = BearerTransport(tokenUrl=\"auth/jwt/login\")\ncookie_transport = CookieTransport(cookie_max_age=3600)\n\ndef get_jwt_strategy() -> JWTStrategy:\n    return JWTStrategy(secret=SECRET, lifetime_seconds=3600)\n\njwt_backend = AuthenticationBackend(\n    name=\"jwt\",\n    transport=bearer_transport,\n    get_strategy=get_jwt_strategy,\n)\ncookie_backend = AuthenticationBackend(\n    name=\"jwt\",\n    transport=cookie_transport,\n    get_strategy=get_jwt_strategy,\n)\n\nasync def get_enabled_backends(request: Request):\n    \"\"\"Return the enabled dependencies following custom logic.\"\"\"\n    if request.url.path == \"/protected-route-only-jwt\":\n        return [jwt_backend]\n    else:\n        return [cookie_backend, jwt_backend]\n\n\ncurrent_active_user = fastapi_users.current_user(active=True, get_enabled_backends=get_enabled_backends)\n\n\n@app.get(\"/protected-route\")\ndef protected_route(user: User = Depends(current_active_user)):\n    return f\"Hello, {user.email}. You are authenticated with a cookie or a JWT.\"\n\n\n@app.get(\"/protected-route-only-jwt\")\ndef protected_route(user: User = Depends(current_active_user)):\n    return f\"Hello, {user.email}. You are authenticated with a JWT.\"\n
"},{"location":"usage/current-user/#in-a-path-operation","title":"In a path operation","text":"

If you don't need the user in the route logic, you can use this syntax:

@app.get(\"/protected-route\", dependencies=[Depends(current_superuser)])\ndef protected_route():\n    return \"Hello, some user.\"\n

You can read more about this in FastAPI docs.

"},{"location":"usage/flow/","title":"Flow","text":"

This page will present you a complete registration and authentication flow once you've setup FastAPI Users. Each example will be presented with a cURL and an axios example.

"},{"location":"usage/flow/#1-registration","title":"1. Registration","text":"

First step, of course, is to register as a user.

"},{"location":"usage/flow/#request","title":"Request","text":"cURLaxios
curl \\\n-H \"Content-Type: application/json\" \\\n-X POST \\\n-d \"{\\\"email\\\": \\\"king.arthur@camelot.bt\\\",\\\"password\\\": \\\"guinevere\\\"}\" \\\nhttp://localhost:8000/auth/register\n
axios.post('http://localhost:8000/auth/register', {\n    email: 'king.arthur@camelot.bt',\n    password: 'guinevere',\n})\n.then((response) => console.log(response))\n.catch((error) => console.log(error));\n
"},{"location":"usage/flow/#response","title":"Response","text":"

You'll get a JSON response looking like this:

{\n    \"id\": \"4fd3477b-eccf-4ee3-8f7d-68ad72261476\",\n    \"email\": \"king.arthur@camelot.bt\",\n    \"is_active\": true,\n    \"is_superuser\": false\n}\n

Info

Several things to bear in mind:

  • If you have defined other required fields in your User model (like a first name or a birthdate), you'll have to provide them in the payload.
  • The user is active by default.
  • The user cannot set is_active or is_superuser itself at registration. Only a superuser can do it by PATCHing the user.
"},{"location":"usage/flow/#2-login","title":"2. Login","text":"

Now, you can login as this new user.

You can generate a login route for each authentication backend. Each backend will have a different response.

"},{"location":"usage/flow/#bearer-jwt","title":"Bearer + JWT","text":""},{"location":"usage/flow/#request_1","title":"Request","text":"cURLaxios
curl \\\n-H \"Content-Type: multipart/form-data\" \\\n-X POST \\\n-F \"username=king.arthur@camelot.bt\" \\\n-F \"password=guinevere\" \\\nhttp://localhost:8000/auth/jwt/login\n
const formData = new FormData();\nformData.set('username', 'king.arthur@camelot.bt');\nformData.set('password', 'guinevere');\naxios.post(\n    'http://localhost:8000/auth/jwt/login',\n    formData,\n    {\n        headers: {\n            'Content-Type': 'multipart/form-data',\n        },\n    },\n)\n.then((response) => console.log(response))\n.catch((error) => console.log(error));\n

Warning

Notice that we don't send it as a JSON payload here but with form data instead. Also, the email is provided by a field named username.

"},{"location":"usage/flow/#response_1","title":"Response","text":"

You'll get a JSON response looking like this:

{\n    \"access_token\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiNGZkMzQ3N2ItZWNjZi00ZWUzLThmN2QtNjhhZDcyMjYxNDc2IiwiYXVkIjoiZmFzdGFwaS11c2VyczphdXRoIiwiZXhwIjoxNTg3ODE4NDI5fQ.anO3JR8-WYCozZ4_2-PQ2Ov9O38RaLP2RAzQIiZhteM\",\n    \"token_type\": \"bearer\"\n}\n

You can use this token to make authenticated requests as the user king.arthur@camelot.bt. We'll see how in the next section.

"},{"location":"usage/flow/#cookie-jwt","title":"Cookie + JWT","text":""},{"location":"usage/flow/#request_2","title":"Request","text":"cURLaxios
curl \\\n-v \\\n-H \"Content-Type: multipart/form-data\" \\\n-X POST \\\n-F \"username=king.arthur@camelot.bt\" \\\n-F \"password=guinevere\" \\\nhttp://localhost:8000/auth/cookie/login\n
const formData = new FormData();\nformData.set('username', 'king.arthur@camelot.bt');\nformData.set('password', 'guinevere');\naxios.post(\n    'http://localhost:8000/auth/cookie/login',\n    formData,\n    {\n        headers: {\n            'Content-Type': 'multipart/form-data',\n        },\n    },\n)\n.then((response) => console.log(response))\n.catch((error) => console.log(error));\n

Warning

Notice that we don't send it as a JSON payload here but with form data instead. Also, the email is provided by a field named username.

"},{"location":"usage/flow/#response_2","title":"Response","text":"

You'll get an empty response. However, the response will come with a Set-Cookie header (that's why we added the -v option in cURL to see them).

set-cookie: fastapiusersauth=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiYzYwNjBmMTEtNTM0OS00YTI0LThiNGEtYTJhODc1ZGM1Mzk1IiwiYXVkIjoiZmFzdGFwaS11c2VyczphdXRoIiwiZXhwIjoxNTg3ODE4OTQ3fQ.qNA4oPVYhoqrJIk-zvAyEfEVoEnP156G30H_SWEU0sU; HttpOnly; Max-Age=3600; Path=/; Secure\n

You can make authenticated requests as the user king.arthur@camelot.bt by setting a Cookie header with this cookie.

Tip

The cookie backend is more suited for browsers, as they handle them automatically. This means that if you make a login request in the browser, it will automatically store the cookie and automatically send it in subsequent requests.

"},{"location":"usage/flow/#3-get-my-profile","title":"3. Get my profile","text":"

Now that we can authenticate, we can get our own profile data. Depending on your authentication backend, the method to authenticate the request will vary. We'll stick with JWT from now on.

"},{"location":"usage/flow/#request_3","title":"Request","text":"cURLaxios
export TOKEN=\"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiNGZkMzQ3N2ItZWNjZi00ZWUzLThmN2QtNjhhZDcyMjYxNDc2IiwiYXVkIjoiZmFzdGFwaS11c2VyczphdXRoIiwiZXhwIjoxNTg3ODE4NDI5fQ.anO3JR8-WYCozZ4_2-PQ2Ov9O38RaLP2RAzQIiZhteM\";\ncurl \\\n-H \"Content-Type: application/json\" \\\n-H \"Authorization: Bearer $TOKEN\" \\\n-X GET \\\nhttp://localhost:8000/users/me\n
const TOKEN = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiNGZkMzQ3N2ItZWNjZi00ZWUzLThmN2QtNjhhZDcyMjYxNDc2IiwiYXVkIjoiZmFzdGFwaS11c2VyczphdXRoIiwiZXhwIjoxNTg3ODE4NDI5fQ.anO3JR8-WYCozZ4_2-PQ2Ov9O38RaLP2RAzQIiZhteM';\naxios.get(\n    'http://localhost:8000/users/me', {\n    headers: {\n        'Authorization': `Bearer ${TOKEN}`,\n    },\n})\n.then((response) => console.log(response))\n.catch((error) => console.log(error));\n
"},{"location":"usage/flow/#response_3","title":"Response","text":"

You'll get a JSON response looking like this:

{\n    \"id\": \"4fd3477b-eccf-4ee3-8f7d-68ad72261476\",\n    \"email\": \"king.arthur@camelot.bt\",\n    \"is_active\": true,\n    \"is_superuser\": false\n}\n

Tip

If you use one of the dependency callable to protect one of your own endpoint, you'll have to authenticate exactly in the same way.

"},{"location":"usage/flow/#4-update-my-profile","title":"4. Update my profile","text":"

We can also update our own profile. For example, we can change our password like this.

"},{"location":"usage/flow/#request_4","title":"Request","text":"cURLaxios
curl \\\n-H \"Content-Type: application/json\" \\\n-H \"Authorization: Bearer $TOKEN\" \\\n-X PATCH \\\n-d \"{\\\"password\\\": \\\"lancelot\\\"}\" \\\nhttp://localhost:8000/users/me\n
axios.patch(\n    'http://localhost:8000/users/me',\n    {\n        password: 'lancelot',\n    },\n    {\n        headers: {\n            'Authorization': `Bearer ${TOKEN}`,\n        },\n    },\n)\n.then((response) => console.log(response))\n.catch((error) => console.log(error));\n
"},{"location":"usage/flow/#response_4","title":"Response","text":"

You'll get a JSON response looking like this:

{\n    \"id\": \"4fd3477b-eccf-4ee3-8f7d-68ad72261476\",\n    \"email\": \"king.arthur@camelot.bt\",\n    \"is_active\": true,\n    \"is_superuser\": false\n}\n

Info

Once again, the user cannot set is_active or is_superuser itself. Only a superuser can do it by PATCHing the user.

"},{"location":"usage/flow/#5-become-a-superuser","title":"5. Become a superuser \ud83e\uddb8\ud83c\udffb\u200d\u2642\ufe0f","text":"

If you want to manage the users of your application, you'll have to become a superuser.

The very first superuser can only be set at database level: open it through a CLI or a GUI, find your user and set the is_superuser column/property to true.

"},{"location":"usage/flow/#51-get-the-profile-of-any-user","title":"5.1. Get the profile of any user","text":"

Now that you are a superuser, you can leverage the power of superuser routes. You can for example get the profile of any user in the database given its id.

"},{"location":"usage/flow/#request_5","title":"Request","text":"cURLaxios
curl \\\n-H \"Content-Type: application/json\" \\\n-H \"Authorization: Bearer $TOKEN\" \\\n-X GET \\\nhttp://localhost:8000/users/4fd3477b-eccf-4ee3-8f7d-68ad72261476\n
axios.get(\n    'http://localhost:8000/users/4fd3477b-eccf-4ee3-8f7d-68ad72261476', {\n    headers: {\n        'Authorization': `Bearer ${TOKEN}`,\n    },\n})\n.then((response) => console.log(response))\n.catch((error) => console.log(error));\n
"},{"location":"usage/flow/#response_5","title":"Response","text":"

You'll get a JSON response looking like this:

{\n    \"id\": \"4fd3477b-eccf-4ee3-8f7d-68ad72261476\",\n    \"email\": \"king.arthur@camelot.bt\",\n    \"is_active\": true,\n    \"is_superuser\": false\n}\n
"},{"location":"usage/flow/#51-update-any-user","title":"5.1. Update any user","text":"

We can now update the profile of any user. For example, we can promote it as superuser.

"},{"location":"usage/flow/#request_6","title":"Request","text":"cURLaxios
curl \\\n-H \"Content-Type: application/json\" \\\n-H \"Authorization: Bearer $TOKEN\" \\\n-X PATCH \\\n -d \"{\\\"is_superuser\\\": true}\" \\\nhttp://localhost:8000/users/4fd3477b-eccf-4ee3-8f7d-68ad72261476\n
axios.patch(\n    'http://localhost:8000/users/4fd3477b-eccf-4ee3-8f7d-68ad72261476',\n    {\n        is_superuser: true,\n    },\n    {\n        headers: {\n            'Authorization': `Bearer ${TOKEN}`,\n        },\n    },\n)\n.then((response) => console.log(response))\n.catch((error) => console.log(error));\n
"},{"location":"usage/flow/#response_6","title":"Response","text":"

You'll get a JSON response looking like this:

{\n    \"id\": \"4fd3477b-eccf-4ee3-8f7d-68ad72261476\",\n    \"email\": \"king.arthur@camelot.bt\",\n    \"is_active\": true,\n    \"is_superuser\": true\n}\n
"},{"location":"usage/flow/#52-delete-any-user","title":"5.2. Delete any user","text":"

Finally, we can delete a user.

"},{"location":"usage/flow/#request_7","title":"Request","text":"cURLaxios
curl \\\n-H \"Content-Type: application/json\" \\\n-H \"Authorization: Bearer $TOKEN\" \\\n-X DELETE \\\nhttp://localhost:8000/users/4fd3477b-eccf-4ee3-8f7d-68ad72261476\n
axios.delete(\n    'http://localhost:8000/users/4fd3477b-eccf-4ee3-8f7d-68ad72261476',\n    {\n        headers: {\n            'Authorization': `Bearer ${TOKEN}`,\n        },\n    },\n)\n.then((response) => console.log(response))\n.catch((error) => console.log(error));\n
"},{"location":"usage/flow/#response_7","title":"Response","text":"

You'll get an empty response.

"},{"location":"usage/flow/#6-logout","title":"6. Logout","text":"

We can also end the session.

"},{"location":"usage/flow/#request_8","title":"Request","text":"cURLaxios
curl \\\n-H \"Content-Type: application/json\" \\\n-H \"Cookie: fastapiusersauth=$TOKEN\" \\\n-X POST \\\nhttp://localhost:8000/auth/cookie/logout\n
axios.post('http://localhost:8000/auth/cookie/logout',\n    null,\n    {\n        headers: {\n            'Cookie': `fastapiusersauth=${TOKEN}`,\n        },\n    }\n)\n.then((response) => console.log(response))\n.catch((error) => console.log(error));\n
"},{"location":"usage/flow/#response_8","title":"Response","text":"

You'll get an empty response.

"},{"location":"usage/flow/#conclusion","title":"Conclusion","text":"

That's it! You now have a good overview of how you can manage the users through the API. Be sure to check the Routes page to have all the details about each endpoints.

"},{"location":"usage/routes/","title":"Routes","text":"

You'll find here the routes exposed by FastAPI Users. Note that you can also review them through the interactive API docs.

"},{"location":"usage/routes/#auth-router","title":"Auth router","text":"

Each authentication backend you generate a router for will produce the following routes. Take care about the prefix you gave it, especially if you have several backends.

"},{"location":"usage/routes/#post-login","title":"POST /login","text":"

Login a user against the method named name. Check the corresponding authentication method to view the success response.

Payload (application/x-www-form-urlencoded)

username=king.arthur@camelot.bt&password=guinevere\n

422 Validation Error

400 Bad Request

Bad credentials or the user is inactive.

{\n    \"detail\": \"LOGIN_BAD_CREDENTIALS\"\n}\n

400 Bad Request

The user is not verified.

{\n    \"detail\": \"LOGIN_USER_NOT_VERIFIED\"\n}\n
"},{"location":"usage/routes/#post-logout","title":"POST /logout","text":"

Logout the authenticated user against the method named name. Check the corresponding authentication method to view the success response.

401 Unauthorized

Missing token or inactive user.

204 No content

The logout process was successful.

"},{"location":"usage/routes/#register-router","title":"Register router","text":""},{"location":"usage/routes/#post-register","title":"POST /register","text":"

Register a new user. Will call the on_after_register handler on successful registration.

Payload

{\n    \"email\": \"king.arthur@camelot.bt\",\n    \"password\": \"guinevere\"\n}\n

201 Created

{\n    \"id\": \"57cbb51a-ab71-4009-8802-3f54b4f2e23\",\n    \"email\": \"king.arthur@camelot.bt\",\n    \"is_active\": true,\n    \"is_superuser\": false\n}\n

422 Validation Error

400 Bad Request

A user already exists with this email.

{\n    \"detail\": \"REGISTER_USER_ALREADY_EXISTS\"\n}\n

400 Bad Request

Password validation failed.

{\n    \"detail\": {\n        \"code\": \"REGISTER_INVALID_PASSWORD\",\n        \"reason\": \"Password should be at least 3 characters\"\n    }\n}\n
"},{"location":"usage/routes/#reset-password-router","title":"Reset password router","text":""},{"location":"usage/routes/#post-forgot-password","title":"POST /forgot-password","text":"

Request a reset password procedure. Will generate a temporary token and call the on_after_forgot_password handler if the user exists.

To prevent malicious users from guessing existing users in your database, the route will always return a 202 Accepted response, even if the user requested does not exist.

Payload

{\n    \"email\": \"king.arthur@camelot.bt\"\n}\n

202 Accepted

"},{"location":"usage/routes/#post-reset-password","title":"POST /reset-password","text":"

Reset a password. Requires the token generated by the /forgot-password route.

Payload

{\n    \"token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiOTIyMWZmYzktNjQwZi00MzcyLTg2ZDMtY2U2NDJjYmE1NjAzIiwiYXVkIjoiZmFzdGFwaS11c2VyczphdXRoIiwiZXhwIjoxNTcxNTA0MTkzfQ.M10bjOe45I5Ncu_uXvOmVV8QxnL-nZfcH96U90JaocI\",\n    \"password\": \"merlin\"\n}\n

200 OK

422 Validation Error

400 Bad Request

Bad or expired token.

{\n    \"detail\": \"RESET_PASSWORD_BAD_TOKEN\"\n}\n

400 Bad Request

Password validation failed.

{\n    \"detail\": {\n        \"code\": \"RESET_PASSWORD_INVALID_PASSWORD\",\n        \"reason\": \"Password should be at least 3 characters\"\n    }\n}\n
"},{"location":"usage/routes/#verify-router","title":"Verify router","text":""},{"location":"usage/routes/#post-request-verify-token","title":"POST /request-verify-token","text":"

Request a user to verify their e-mail. Will generate a temporary token and call the on_after_request_verify handler if the user exists, active and not already verified.

To prevent malicious users from guessing existing users in your database, the route will always return a 202 Accepted response, even if the user requested does not exist, not active or already verified.

Payload

{\n    \"email\": \"king.arthur@camelot.bt\"\n}\n

202 Accepted

"},{"location":"usage/routes/#post-verify","title":"POST /verify","text":"

Verify a user. Requires the token generated by the /request-verify-token route. Will call the call the on_after_verify handler on success.

Payload

{\n    \"token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiOTIyMWZmYzktNjQwZi00MzcyLTg2ZDMtY2U2NDJjYmE1NjAzIiwiYXVkIjoiZmFzdGFwaS11c2VyczphdXRoIiwiZXhwIjoxNTcxNTA0MTkzfQ.M10bjOe45I5Ncu_uXvOmVV8QxnL-nZfcH96U90JaocI\"\n}\n

200 OK

422 Validation Error

400 Bad Request

Bad token, not existing user or not the e-mail currently set for the user.

{\n    \"detail\": \"VERIFY_USER_BAD_TOKEN\"\n}\n

400 Bad Request

The user is already verified.

{\n    \"detail\": \"VERIFY_USER_ALREADY_VERIFIED\"\n}\n
"},{"location":"usage/routes/#oauth-router","title":"OAuth router","text":"

Each OAuth router you define will expose the two following routes.

"},{"location":"usage/routes/#get-authorize","title":"GET /authorize","text":"

Return the authorization URL for the OAuth service where you should redirect your user.

Query parameters

  • scopes: Optional list of scopes to ask for. Expected format: scopes=a&scopes=b.

200 OK

{\n    \"authorization_url\": \"https://www.tintagel.bt/oauth/authorize?client_id=CLIENT_ID&scopes=a+b&redirect_uri=https://www.camelot.bt/oauth/callback\"\n}\n
"},{"location":"usage/routes/#get-callback","title":"GET /callback","text":"

Handle the OAuth callback.

Query parameters

  • code: OAuth callback code.
  • state: State token.
  • error: OAuth error.

Depending on the situation, several things can happen:

  • The OAuth account exists in database and is linked to a user:
    • OAuth account is updated in database with fresh access token.
    • The user is authenticated following the chosen authentication method.
  • The OAuth account doesn't exist in database but a user with the same email address exists:
    • By default, an HTTP 400 error is raised.
    • If the associate_by_email flag is set to True on the router declaration, OAuth account is linked to the user. The user is authenticated following the chosen authentication method.
  • The OAuth account doesn't exist in database and no user with the email address exists:
    • A new user is created and linked to the OAuth account.
    • The user is authenticated following the chosen authentication method.

400 Bad Request

Invalid token.

400 Bad Request

The OAuth provider didn't return an e-mail address. Make sure this provider return e-mail address through their API and you have asked for the required scope.

{\n    \"detail\": \"OAUTH_NOT_AVAILABLE_EMAIL\"\n}\n

400 Bad Request

Another user with the same e-mail address already exists.

{\n    \"detail\": \"OAUTH_USER_ALREADY_EXISTS\"\n}\n

400 Bad Request

User is inactive.

{\n    \"detail\": \"LOGIN_BAD_CREDENTIALS\"\n}\n
"},{"location":"usage/routes/#oauth-association-router","title":"OAuth association router","text":"

Each OAuth association router you define will expose the two following routes.

"},{"location":"usage/routes/#get-authorize_1","title":"GET /authorize","text":"

Return the authorization URL for the OAuth service where you should redirect your user.

Query parameters

  • scopes: Optional list of scopes to ask for. Expected format: scopes=a&scopes=b.

401 Unauthorized

Missing token or inactive user.

200 OK

{\n    \"authorization_url\": \"https://www.tintagel.bt/oauth/authorize?client_id=CLIENT_ID&scopes=a+b&redirect_uri=https://www.camelot.bt/oauth/callback\"\n}\n
"},{"location":"usage/routes/#get-callback_1","title":"GET /callback","text":"

Handle the OAuth callback and add the OAuth account to the current authenticated active user.

Query parameters

  • code: OAuth callback code.
  • state: State token.
  • error: OAuth error.

401 Unauthorized

Missing token or inactive user.

400 Bad Request

Invalid token.

400 Bad Request

The OAuth provider didn't return an e-mail address. Make sure this provider return e-mail address through their API and you have asked for the required scope.

{\n    \"detail\": \"OAUTH_NOT_AVAILABLE_EMAIL\"\n}\n

200 OK

{\n    \"id\": \"57cbb51a-ab71-4009-8802-3f54b4f2e23\",\n    \"email\": \"king.arthur@tintagel.bt\",\n    \"is_active\": true,\n    \"is_superuser\": false,\n    \"oauth_accounts\": [\n        {\n            \"id\": \"6c98caf5-9bc5-4c4f-8a45-a0ae0c40cd77\",\n            \"oauth_name\": \"TINTAGEL\",\n            \"access_token\": \"ACCESS_TOKEN\",\n            \"expires_at\": \"1641040620\",\n            \"account_id\": \"king_arthur_tintagel\",\n            \"account_email\": \"king.arthur@tintagel.bt\"\n        }\n    ]\n}\n
"},{"location":"usage/routes/#users-router","title":"Users router","text":""},{"location":"usage/routes/#get-me","title":"GET /me","text":"

Return the current authenticated active user.

200 OK

{\n    \"id\": \"57cbb51a-ab71-4009-8802-3f54b4f2e23\",\n    \"email\": \"king.arthur@camelot.bt\",\n    \"is_active\": true,\n    \"is_superuser\": false\n}\n

401 Unauthorized

Missing token or inactive user.

"},{"location":"usage/routes/#patch-me","title":"PATCH /me","text":"

Update the current authenticated active user.

Payload

{\n    \"email\": \"king.arthur@tintagel.bt\",\n    \"password\": \"merlin\"\n}\n

200 OK

{\n    \"id\": \"57cbb51a-ab71-4009-8802-3f54b4f2e23\",\n    \"email\": \"king.arthur@tintagel.bt\",\n    \"is_active\": true,\n    \"is_superuser\": false\n}\n

401 Unauthorized

Missing token or inactive user.

400 Bad Request

Password validation failed.

{\n    \"detail\": {\n        \"code\": \"UPDATE_USER_INVALID_PASSWORD\",\n        \"reason\": \"Password should be at least 3 characters\"\n    }\n}\n

400 Bad Request

A user with this email already exists.

{\n    \"detail\": \"UPDATE_USER_EMAIL_ALREADY_EXISTS\"\n}\n

422 Validation Error

"},{"location":"usage/routes/#get-user_id","title":"GET /{user_id}","text":"

Return the user with id user_id.

200 OK

{\n    \"id\": \"57cbb51a-ab71-4009-8802-3f54b4f2e23\",\n    \"email\": \"king.arthur@camelot.bt\",\n    \"is_active\": true,\n    \"is_superuser\": false\n}\n

401 Unauthorized

Missing token or inactive user.

403 Forbidden

Not a superuser.

404 Not found

The user does not exist.

"},{"location":"usage/routes/#patch-user_id","title":"PATCH /{user_id}","text":"

Update the user with id user_id.

Payload

{\n    \"email\": \"king.arthur@tintagel.bt\",\n    \"password\": \"merlin\",\n    \"is_active\": false,\n    \"is_superuser\": true\n}\n

200 OK

{\n    \"id\": \"57cbb51a-ab71-4009-8802-3f54b4f2e23\",\n    \"email\": \"king.arthur@camelot.bt\",\n    \"is_active\": false,\n    \"is_superuser\": true\n}\n

401 Unauthorized

Missing token or inactive user.

403 Forbidden

Not a superuser.

404 Not found

The user does not exist.

400 Bad Request

Password validation failed.

{\n    \"detail\": {\n        \"code\": \"UPDATE_USER_INVALID_PASSWORD\",\n        \"reason\": \"Password should be at least 3 characters\"\n    }\n}\n

400 Bad Request

A user with this email already exists.

{\n    \"detail\": \"UPDATE_USER_EMAIL_ALREADY_EXISTS\"\n}\n

"},{"location":"usage/routes/#delete-user_id","title":"DELETE /{user_id}","text":"

Delete the user with id user_id.

204 No content

401 Unauthorized

Missing token or inactive user.

403 Forbidden

Not a superuser.

404 Not found

The user does not exist.

"}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"FastAPI Users","text":"

Ready-to-use and customizable users management for FastAPI

Documentation: https://fastapi-users.github.io/fastapi-users/

Source Code: https://github.com/fastapi-users/fastapi-users

Add quickly a registration and authentication system to your FastAPI project. FastAPI Users is designed to be as customizable and adaptable as possible.

"},{"location":"#features","title":"Features","text":"
  • Extensible base user model
  • Ready-to-use register, login, reset password and verify e-mail routes
  • Ready-to-use social OAuth2 login flow
  • Dependency callables to inject current user in route
  • Pluggable password validation
  • Customizable database backend
    • SQLAlchemy ORM async included
    • MongoDB with Beanie ODM included
  • Multiple customizable authentication backends
    • Transports: Authorization header, Cookie
    • Strategies: JWT, Database, Redis
  • Full OpenAPI schema support, even with several authentication backends
"},{"location":"#in-a-hurry-discover-fief-the-open-source-authentication-platform","title":"In a hurry? Discover Fief, the open-source authentication platform","text":"

Implementing registration, login, social auth is hard and painful. We know it. With our highly secure and open-source users management platform, you can focus on your app while staying in control of your users data.

  • Open-source: self-host it for free
  • Pre-built login and registration pages: clean and fast authentication so you don't have to do it yourself
  • Official Python client with built-in FastAPI integration

It's free and open-source

"},{"location":"#contributors-and-sponsors","title":"Contributors and sponsors \u2728\u2615\ufe0f","text":"

Thanks goes to these wonderful people (emoji key):

Fran\u00e7ois Voron\ud83d\udea7 Paolo Dina\ud83d\udcb5 \ud83d\udcbb Dmytro Ohorodnik\ud83d\udc1b Matthew D. Scholefield\ud83d\udc1b \ud83d\udcbb roywes\ud83d\udc1b \ud83d\udcbb Satwik Kansal\ud83d\udcd6 Edd Salkield\ud83d\udcbb \ud83d\udcd6 mark-todd\ud83d\udcbb \ud83d\udcd6 lill74\ud83d\udc1b \ud83d\udcbb \ud83d\udcd6 SelfhostedPro\ud83d\udee1\ufe0f \ud83d\udcbb Oskar Gmerek\ud83d\udcd6 Martin Collado\ud83d\udc1b \ud83d\udcbb Eric Lopes\ud83d\udcd6 \ud83d\udee1\ufe0f Beau Breon\ud83d\udcbb Niyas Mohammed\ud83d\udcd6 prostomarkeloff\ud83d\udcd6 \ud83d\udcbb Marius M\u00e9zerette\ud83d\udc1b \ud83e\udd14 Nickolas Grigoriadis\ud83d\udc1b Open Data Coder\ud83e\udd14 Mohammed Alshehri\ud83e\udd14 Tyler Renelle\ud83e\udd14 collerek\ud83d\udcbb Robert Bracco\ud83d\udcb5 Augusto Herrmann\ud83d\udcd6 Smithybrewer\ud83d\udc1b silllli\ud83d\udcd6 alexferrari88\ud83d\udcb5 sandalwoodbox\ud83d\udc1b \ud83d\udcd6 Vlad Hoi\ud83d\udcd6 Joe Nudell\ud83d\udc1b Ben\ud83d\udcbb BoYanZh\ud83d\udcd6 David Brochart\ud83d\udcd6 \ud83d\udcbb Daan Beverdam\ud83d\udcbb St\u00e9phane Raimbault\u26a0\ufe0f \ud83d\udc1b Sondre Lilleb\u00f8 Gundersen\ud83d\udcd6 Maxim\ud83d\udcd6 \ud83d\udc1b scottdavort\ud83d\udcb5 John Dukewich\ud83d\udcd6 Yasser Tahiri\ud83d\udcbb Brandon H. Goding\ud83d\udcbb \ud83d\udcd6 PovilasK\ud83d\udcbb Just van den Broecke\ud83d\udcb5 jakemanger\ud83d\udc1b \ud83d\udcbb Ikko Ashimine\ud83d\udcbb Maty\u00e1\u0161 Richter\ud83d\udcbb Hazedd\ud83d\udc1b \ud83d\udcd6 Luis Roel\ud83d\udcb5 Alexandr Makurin\ud83d\udcbb \ud83d\udc1b Leon Thurner\ud83d\udcd6 Goran Meki\u0107\ud83d\udce6 Gaganpreet\ud83d\udcbb Joe Taylor\ud83d\udcbb Richard Friberg\ud83d\udc1b Kenton Parton\ud83d\udcb5 Adrian Cio\u0142ek\ud83d\udc1b \u2b55Alexander Rymdeko-Harvey\ud83d\udcd6 schwannden\ud83d\udea7 \ud83d\udcbb Jimmy Angel P\u00e9rez D\u00edaz\ud83d\udee1\ufe0f Austin Orr\ud83d\udea7 Carlo Eugster\ud83d\udee1\ufe0f Vittorio Zamboni\ud83d\udcbb Andrey\ud83d\udcd6 Can H. Tartanoglu\ud83d\udc1b Filipe Nascimento\ud83d\udee1\ufe0f dudulu\ud83d\udcb5 \ud83d\udc1b \ud83d\udcac Toni Alatalo\ud83d\udcbb \ud83d\udcd6 B\u00f6rge Kiss\ud83d\udcd6 Guilherme Caminha\ud83d\udcd6 T\u00e9va KRIEF\ud83d\udcbb Essa Alshammri\ud83d\udcd6 0xJan\ud83d\udc1b Justin Thomas\ud83d\udcbb Adam Israel\ud83d\udcbb Nerixjk\ud83d\udc1b \ud83d\udcbb Mike Fotinakis\ud83d\udcbb \ud83d\udc1b lifengmds\ud83d\udcb5 raindata5\ud83d\udcd6 Mark Donnelly\ud83d\udcd6 Alexander Zinov\ud83d\udcbb

This project follows the all-contributors specification. Contributions of any kind welcome!

"},{"location":"#development","title":"Development","text":""},{"location":"#setup-environment","title":"Setup environment","text":"

We use Hatch to manage the development environment and production build. Ensure it's installed on your system.

"},{"location":"#run-unit-tests","title":"Run unit tests","text":"

You can run all the tests with:

hatch run test:test\n
"},{"location":"#format-the-code","title":"Format the code","text":"

Execute the following command to apply linting and check typing:

hatch run lint\n
"},{"location":"#serve-the-documentation","title":"Serve the documentation","text":"

You can serve the documentation locally with the following command:

hatch run docs\n

The documentation will be available on http://localhost:8000.

"},{"location":"#license","title":"License","text":"

This project is licensed under the terms of the MIT license.

"},{"location":"installation/","title":"Installation","text":"

You can add FastAPI Users to your FastAPI project in a few easy steps. First of all, install the dependency:

"},{"location":"installation/#with-sqlalchemy-support","title":"With SQLAlchemy support","text":"
pip install 'fastapi-users[sqlalchemy]'\n
"},{"location":"installation/#with-beanie-support","title":"With Beanie support","text":"
pip install 'fastapi-users[beanie]'\n
"},{"location":"installation/#with-redis-authentication-backend-support","title":"With Redis authentication backend support","text":"

Information on installing with proper database support can be found in the Redis section.

"},{"location":"installation/#with-oauth2-support","title":"With OAuth2 support","text":"

Information on installing with proper database support can be found in the OAuth2 section.

That's it! In the next section, we'll have an overview of how things work.

"},{"location":"configuration/full-example/","title":"Full example","text":"

Here is a full working example with JWT authentication to help get you started.

Warning

Notice that SECRET should be changed to a strong passphrase. Insecure passwords may give attackers full access to your database.

"},{"location":"configuration/full-example/#sqlalchemy","title":"SQLAlchemy","text":"

Open

requirements.txtmain.pyapp/app.pyapp/db.pyapp/schemas.pyapp/users.py
fastapi\nfastapi-users[sqlalchemy]\nuvicorn[standard]\naiosqlite\n
import uvicorn\n\nif __name__ == \"__main__\":\n    uvicorn.run(\"app.app:app\", host=\"0.0.0.0\", log_level=\"info\")\n
from contextlib import asynccontextmanager\n\nfrom fastapi import Depends, FastAPI\n\nfrom app.db import User, create_db_and_tables\nfrom app.schemas import UserCreate, UserRead, UserUpdate\nfrom app.users import auth_backend, current_active_user, fastapi_users\n\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    # Not needed if you setup a migration system like Alembic\n    await create_db_and_tables()\n    yield\n\n\napp = FastAPI(lifespan=lifespan)\n\napp.include_router(\n    fastapi_users.get_auth_router(auth_backend), prefix=\"/auth/jwt\", tags=[\"auth\"]\n)\napp.include_router(\n    fastapi_users.get_register_router(UserRead, UserCreate),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\napp.include_router(\n    fastapi_users.get_reset_password_router(),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\napp.include_router(\n    fastapi_users.get_verify_router(UserRead),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\napp.include_router(\n    fastapi_users.get_users_router(UserRead, UserUpdate),\n    prefix=\"/users\",\n    tags=[\"users\"],\n)\n\n\n@app.get(\"/authenticated-route\")\nasync def authenticated_route(user: User = Depends(current_active_user)):\n    return {\"message\": f\"Hello {user.email}!\"}\n
from collections.abc import AsyncGenerator\n\nfrom fastapi import Depends\nfrom fastapi_users.db import SQLAlchemyBaseUserTableUUID, SQLAlchemyUserDatabase\nfrom sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine\nfrom sqlalchemy.orm import DeclarativeBase\n\nDATABASE_URL = \"sqlite+aiosqlite:///./test.db\"\n\n\nclass Base(DeclarativeBase):\n    pass\n\n\nclass User(SQLAlchemyBaseUserTableUUID, Base):\n    pass\n\n\nengine = create_async_engine(DATABASE_URL)\nasync_session_maker = async_sessionmaker(engine, expire_on_commit=False)\n\n\nasync def create_db_and_tables():\n    async with engine.begin() as conn:\n        await conn.run_sync(Base.metadata.create_all)\n\n\nasync def get_async_session() -> AsyncGenerator[AsyncSession, None]:\n    async with async_session_maker() as session:\n        yield session\n\n\nasync def get_user_db(session: AsyncSession = Depends(get_async_session)):\n    yield SQLAlchemyUserDatabase(session, User)\n
import uuid\n\nfrom fastapi_users import schemas\n\n\nclass UserRead(schemas.BaseUser[uuid.UUID]):\n    pass\n\n\nclass UserCreate(schemas.BaseUserCreate):\n    pass\n\n\nclass UserUpdate(schemas.BaseUserUpdate):\n    pass\n
import uuid\nfrom typing import Optional\n\nfrom fastapi import Depends, Request\nfrom fastapi_users import BaseUserManager, FastAPIUsers, UUIDIDMixin, models\nfrom fastapi_users.authentication import (\n    AuthenticationBackend,\n    BearerTransport,\n    JWTStrategy,\n)\nfrom fastapi_users.db import SQLAlchemyUserDatabase\n\nfrom app.db import User, get_user_db\n\nSECRET = \"SECRET\"\n\n\nclass UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):\n    reset_password_token_secret = SECRET\n    verification_token_secret = SECRET\n\n    async def on_after_register(self, user: User, request: Optional[Request] = None):\n        print(f\"User {user.id} has registered.\")\n\n    async def on_after_forgot_password(\n        self, user: User, token: str, request: Optional[Request] = None\n    ):\n        print(f\"User {user.id} has forgot their password. Reset token: {token}\")\n\n    async def on_after_request_verify(\n        self, user: User, token: str, request: Optional[Request] = None\n    ):\n        print(f\"Verification requested for user {user.id}. Verification token: {token}\")\n\n\nasync def get_user_manager(user_db: SQLAlchemyUserDatabase = Depends(get_user_db)):\n    yield UserManager(user_db)\n\n\nbearer_transport = BearerTransport(tokenUrl=\"auth/jwt/login\")\n\n\ndef get_jwt_strategy() -> JWTStrategy[models.UP, models.ID]:\n    return JWTStrategy(secret=SECRET, lifetime_seconds=3600)\n\n\nauth_backend = AuthenticationBackend(\n    name=\"jwt\",\n    transport=bearer_transport,\n    get_strategy=get_jwt_strategy,\n)\n\nfastapi_users = FastAPIUsers[User, uuid.UUID](get_user_manager, [auth_backend])\n\ncurrent_active_user = fastapi_users.current_user(active=True)\n
"},{"location":"configuration/full-example/#beanie","title":"Beanie","text":"

Open

requirements.txtmain.pyapp/app.pyapp/db.pyapp/schemas.pyapp/users.py
fastapi\nfastapi-users[beanie]\nuvicorn[standard]\n
import uvicorn\n\nif __name__ == \"__main__\":\n    uvicorn.run(\"app.app:app\", host=\"0.0.0.0\", log_level=\"info\")\n
from contextlib import asynccontextmanager\n\nfrom beanie import init_beanie\nfrom fastapi import Depends, FastAPI\n\nfrom app.db import User, db\nfrom app.schemas import UserCreate, UserRead, UserUpdate\nfrom app.users import auth_backend, current_active_user, fastapi_users\n\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    await init_beanie(\n        database=db,\n        document_models=[\n            User,\n        ],\n    )\n    yield\n\n\napp = FastAPI(lifespan=lifespan)\n\n\napp.include_router(\n    fastapi_users.get_auth_router(auth_backend), prefix=\"/auth/jwt\", tags=[\"auth\"]\n)\napp.include_router(\n    fastapi_users.get_register_router(UserRead, UserCreate),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\napp.include_router(\n    fastapi_users.get_reset_password_router(),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\napp.include_router(\n    fastapi_users.get_verify_router(UserRead),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\napp.include_router(\n    fastapi_users.get_users_router(UserRead, UserUpdate),\n    prefix=\"/users\",\n    tags=[\"users\"],\n)\n\n\n@app.get(\"/authenticated-route\")\nasync def authenticated_route(user: User = Depends(current_active_user)):\n    return {\"message\": f\"Hello {user.email}!\"}\n
import motor.motor_asyncio\nfrom beanie import Document\nfrom fastapi_users.db import BeanieBaseUser\nfrom fastapi_users_db_beanie import BeanieUserDatabase\n\nDATABASE_URL = \"mongodb://localhost:27017\"\nclient = motor.motor_asyncio.AsyncIOMotorClient(\n    DATABASE_URL, uuidRepresentation=\"standard\"\n)\ndb = client[\"database_name\"]\n\n\nclass User(BeanieBaseUser, Document):\n    pass\n\n\nasync def get_user_db():\n    yield BeanieUserDatabase(User)\n
from beanie import PydanticObjectId\nfrom fastapi_users import schemas\n\n\nclass UserRead(schemas.BaseUser[PydanticObjectId]):\n    pass\n\n\nclass UserCreate(schemas.BaseUserCreate):\n    pass\n\n\nclass UserUpdate(schemas.BaseUserUpdate):\n    pass\n
from typing import Optional\n\nfrom beanie import PydanticObjectId\nfrom fastapi import Depends, Request\nfrom fastapi_users import BaseUserManager, FastAPIUsers\nfrom fastapi_users.authentication import (\n    AuthenticationBackend,\n    BearerTransport,\n    JWTStrategy,\n)\nfrom fastapi_users.db import BeanieUserDatabase, ObjectIDIDMixin\n\nfrom app.db import User, get_user_db\n\nSECRET = \"SECRET\"\n\n\nclass UserManager(ObjectIDIDMixin, BaseUserManager[User, PydanticObjectId]):\n    reset_password_token_secret = SECRET\n    verification_token_secret = SECRET\n\n    async def on_after_register(self, user: User, request: Optional[Request] = None):\n        print(f\"User {user.id} has registered.\")\n\n    async def on_after_forgot_password(\n        self, user: User, token: str, request: Optional[Request] = None\n    ):\n        print(f\"User {user.id} has forgot their password. Reset token: {token}\")\n\n    async def on_after_request_verify(\n        self, user: User, token: str, request: Optional[Request] = None\n    ):\n        print(f\"Verification requested for user {user.id}. Verification token: {token}\")\n\n\nasync def get_user_manager(user_db: BeanieUserDatabase = Depends(get_user_db)):\n    yield UserManager(user_db)\n\n\nbearer_transport = BearerTransport(tokenUrl=\"auth/jwt/login\")\n\n\ndef get_jwt_strategy() -> JWTStrategy:\n    return JWTStrategy(secret=SECRET, lifetime_seconds=3600)\n\n\nauth_backend = AuthenticationBackend(\n    name=\"jwt\",\n    transport=bearer_transport,\n    get_strategy=get_jwt_strategy,\n)\n\nfastapi_users = FastAPIUsers[User, PydanticObjectId](get_user_manager, [auth_backend])\n\ncurrent_active_user = fastapi_users.current_user(active=True)\n
"},{"location":"configuration/full-example/#what-now","title":"What now?","text":"

You're ready to go! Be sure to check the Usage section to understand how to work with FastAPI Users.

"},{"location":"configuration/oauth/","title":"OAuth2","text":"

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

"},{"location":"configuration/oauth/#installation","title":"Installation","text":"

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

pip install 'fastapi-users[sqlalchemy,oauth]'\n
pip install 'fastapi-users[beanie,oauth]'\n
"},{"location":"configuration/oauth/#configuration","title":"Configuration","text":""},{"location":"configuration/oauth/#instantiate-an-oauth2-client","title":"Instantiate an OAuth2 client","text":"

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

from httpx_oauth.clients.google import GoogleOAuth2\n\ngoogle_oauth_client = GoogleOAuth2(\"CLIENT_ID\", \"CLIENT_SECRET\")\n
"},{"location":"configuration/oauth/#setup-the-database-adapter","title":"Setup the database adapter","text":""},{"location":"configuration/oauth/#sqlalchemy","title":"SQLAlchemy","text":"

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

from collections.abc import AsyncGenerator\n\nfrom fastapi import Depends\nfrom fastapi_users.db import (\n    SQLAlchemyBaseOAuthAccountTableUUID,\n    SQLAlchemyBaseUserTableUUID,\n    SQLAlchemyUserDatabase,\n)\nfrom sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine\nfrom sqlalchemy.orm import DeclarativeBase, Mapped, relationship\n\nDATABASE_URL = \"sqlite+aiosqlite:///./test.db\"\n\n\nclass Base(DeclarativeBase):\n    pass\n\n\nclass OAuthAccount(SQLAlchemyBaseOAuthAccountTableUUID, Base):\n    pass\n\n\nclass User(SQLAlchemyBaseUserTableUUID, Base):\n    oauth_accounts: Mapped[list[OAuthAccount]] = relationship(\n        \"OAuthAccount\", lazy=\"joined\"\n    )\n\n\nengine = create_async_engine(DATABASE_URL)\nasync_session_maker = async_sessionmaker(engine, expire_on_commit=False)\n\n\nasync def create_db_and_tables():\n    async with engine.begin() as conn:\n        await conn.run_sync(Base.metadata.create_all)\n\n\nasync def get_async_session() -> AsyncGenerator[AsyncSession, None]:\n    async with async_session_maker() as session:\n        yield session\n\n\nasync def get_user_db(session: AsyncSession = Depends(get_async_session)):\n    yield SQLAlchemyUserDatabase(session, User, OAuthAccount)\n

Notice that we also manually added a relationship on User so that SQLAlchemy can properly retrieve the OAuth accounts of the user.

Besides, when instantiating the database adapter, we need pass this SQLAlchemy model as third argument.

Primary key is defined as UUID

By default, we use UUID as a primary key ID for your user. If you want to use another type, like an auto-incremented integer, you can use SQLAlchemyBaseOAuthAccountTable as base class and define your own id and user_id column.

class OAuthAccount(SQLAlchemyBaseOAuthAccountTable[int], Base):\n    id: Mapped[int] = mapped_column(Integer, primary_key=True)\n\n    @declared_attr\n    def user_id(cls) -> Mapped[int]:\n        return mapped_column(Integer, ForeignKey(\"user.id\", ondelete=\"cascade\"), nullable=False)\n

Notice that SQLAlchemyBaseOAuthAccountTable expects a generic type to define the actual type of ID you use.

"},{"location":"configuration/oauth/#beanie","title":"Beanie","text":"

The advantage of MongoDB is that you can easily embed sub-objects in a single document. That's why the configuration for Beanie is quite simple. All we need to do is to define another class to structure an OAuth account object.

import motor.motor_asyncio\nfrom beanie import Document\nfrom fastapi_users.db import BaseOAuthAccount, BeanieBaseUser, BeanieUserDatabase\nfrom pydantic import Field\n\nDATABASE_URL = \"mongodb://localhost:27017\"\nclient = motor.motor_asyncio.AsyncIOMotorClient(\n    DATABASE_URL, uuidRepresentation=\"standard\"\n)\ndb = client[\"database_name\"]\n\n\nclass OAuthAccount(BaseOAuthAccount):\n    pass\n\n\nclass User(BeanieBaseUser, Document):\n    oauth_accounts: list[OAuthAccount] = Field(default_factory=list)\n\n\nasync def get_user_db():\n    yield BeanieUserDatabase(User, OAuthAccount)\n

It's worth to note that OAuthAccount is not a Beanie document but a Pydantic model that we'll embed inside the User document, through the oauth_accounts array.

"},{"location":"configuration/oauth/#generate-routers","title":"Generate routers","text":"

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

app.include_router(\n    fastapi_users.get_oauth_router(google_oauth_client, auth_backend, \"SECRET\"),\n    prefix=\"/auth/google\",\n    tags=[\"auth\"],\n)\n

Tip

If you have several OAuth clients and/or several authentication backends, you'll need to create a router for each pair you want to support.

"},{"location":"configuration/oauth/#existing-account-association","title":"Existing account association","text":"

If a user with the same e-mail address already exists, an HTTP 400 error will be raised by default.

You can however choose to automatically link this OAuth account to the existing user account by setting the associate_by_email flag:

app.include_router(\n    fastapi_users.get_oauth_router(\n        google_oauth_client,\n        auth_backend,\n        \"SECRET\",\n        associate_by_email=True,\n    ),\n    prefix=\"/auth/google\",\n    tags=[\"auth\"],\n)\n

Bear in mind though that it can lead to security breaches if the OAuth provider does not validate e-mail addresses. How?

  • Let's say your app support an OAuth provider, Merlinbook, which does not validate e-mail addresses.
  • Imagine a user registers to your app with the e-mail address lancelot@camelot.bt.
  • Now, a malicious user creates an account on Merlinbook with the same e-mail address. Without e-mail validation, the malicious user can use this account without limitation.
  • The malicious user authenticates using Merlinbook OAuth on your app, which automatically associates to the existing lancelot@camelot.bt.
  • Now, the malicious user has full access to the user account on your app \ud83d\ude1e
"},{"location":"configuration/oauth/#association-router-for-authenticated-users","title":"Association router for authenticated users","text":"

We also provide a router to associate an already authenticated user with an OAuth account. After this association, the user will be able to authenticate with this OAuth provider.

app.include_router(\n    fastapi_users.get_oauth_associate_router(google_oauth_client, UserRead, \"SECRET\"),\n    prefix=\"/auth/associate/google\",\n    tags=[\"auth\"],\n)\n

Notice that, just like for the Users router, you have to pass the UserRead Pydantic schema.

"},{"location":"configuration/oauth/#set-is_verified-to-true-by-default","title":"Set is_verified to True by default","text":"

This section is only useful if you set up email verification

You can read more about this feature here.

When a new user registers with an OAuth provider, the is_verified flag is set to False, which requires the user to verify its email address.

You can choose to trust the email address given by the OAuth provider and set the is_verified flag to True after registration. You can do this by setting the is_verified_by_default argument:

app.include_router(\n    fastapi_users.get_oauth_router(\n        google_oauth_client,\n        auth_backend,\n        \"SECRET\",\n        is_verified_by_default=True,\n    ),\n    prefix=\"/auth/google\",\n    tags=[\"auth\"],\n)\n

Make sure you can trust the OAuth provider

Make sure the OAuth provider you're using does verify the email address before enabling this flag.

"},{"location":"configuration/oauth/#full-example","title":"Full example","text":"

Warning

Notice that SECRET should be changed to a strong passphrase. Insecure passwords may give attackers full access to your database.

"},{"location":"configuration/oauth/#sqlalchemy_1","title":"SQLAlchemy","text":"

Open

requirements.txtmain.pyapp/app.pyapp/db.pyapp/schemas.pyapp/users.py
fastapi\nfastapi-users[sqlalchemy,oauth]\nuvicorn[standard]\naiosqlite\n
import uvicorn\n\nif __name__ == \"__main__\":\n    uvicorn.run(\"app.app:app\", host=\"0.0.0.0\", log_level=\"info\")\n
from contextlib import asynccontextmanager\n\nfrom fastapi import Depends, FastAPI\n\nfrom app.db import User, create_db_and_tables\nfrom app.schemas import UserCreate, UserRead, UserUpdate\nfrom app.users import (\n    SECRET,\n    auth_backend,\n    current_active_user,\n    fastapi_users,\n    google_oauth_client,\n)\n\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    # Not needed if you setup a migration system like Alembic\n    await create_db_and_tables()\n    yield\n\n\napp = FastAPI(lifespan=lifespan)\n\napp.include_router(\n    fastapi_users.get_auth_router(auth_backend), prefix=\"/auth/jwt\", tags=[\"auth\"]\n)\napp.include_router(\n    fastapi_users.get_register_router(UserRead, UserCreate),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\napp.include_router(\n    fastapi_users.get_reset_password_router(),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\napp.include_router(\n    fastapi_users.get_verify_router(UserRead),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\napp.include_router(\n    fastapi_users.get_users_router(UserRead, UserUpdate),\n    prefix=\"/users\",\n    tags=[\"users\"],\n)\napp.include_router(\n    fastapi_users.get_oauth_router(google_oauth_client, auth_backend, SECRET),\n    prefix=\"/auth/google\",\n    tags=[\"auth\"],\n)\n\n\n@app.get(\"/authenticated-route\")\nasync def authenticated_route(user: User = Depends(current_active_user)):\n    return {\"message\": f\"Hello {user.email}!\"}\n
from collections.abc import AsyncGenerator\n\nfrom fastapi import Depends\nfrom fastapi_users.db import (\n    SQLAlchemyBaseOAuthAccountTableUUID,\n    SQLAlchemyBaseUserTableUUID,\n    SQLAlchemyUserDatabase,\n)\nfrom sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine\nfrom sqlalchemy.orm import DeclarativeBase, Mapped, relationship\n\nDATABASE_URL = \"sqlite+aiosqlite:///./test.db\"\n\n\nclass Base(DeclarativeBase):\n    pass\n\n\nclass OAuthAccount(SQLAlchemyBaseOAuthAccountTableUUID, Base):\n    pass\n\n\nclass User(SQLAlchemyBaseUserTableUUID, Base):\n    oauth_accounts: Mapped[list[OAuthAccount]] = relationship(\n        \"OAuthAccount\", lazy=\"joined\"\n    )\n\n\nengine = create_async_engine(DATABASE_URL)\nasync_session_maker = async_sessionmaker(engine, expire_on_commit=False)\n\n\nasync def create_db_and_tables():\n    async with engine.begin() as conn:\n        await conn.run_sync(Base.metadata.create_all)\n\n\nasync def get_async_session() -> AsyncGenerator[AsyncSession, None]:\n    async with async_session_maker() as session:\n        yield session\n\n\nasync def get_user_db(session: AsyncSession = Depends(get_async_session)):\n    yield SQLAlchemyUserDatabase(session, User, OAuthAccount)\n
import uuid\n\nfrom fastapi_users import schemas\n\n\nclass UserRead(schemas.BaseUser[uuid.UUID]):\n    pass\n\n\nclass UserCreate(schemas.BaseUserCreate):\n    pass\n\n\nclass UserUpdate(schemas.BaseUserUpdate):\n    pass\n
import os\nimport uuid\nfrom typing import Optional\n\nfrom fastapi import Depends, Request\nfrom fastapi_users import BaseUserManager, FastAPIUsers, UUIDIDMixin, models\nfrom fastapi_users.authentication import (\n    AuthenticationBackend,\n    BearerTransport,\n    JWTStrategy,\n)\nfrom fastapi_users.db import SQLAlchemyUserDatabase\nfrom httpx_oauth.clients.google import GoogleOAuth2\n\nfrom app.db import User, get_user_db\n\nSECRET = \"SECRET\"\n\ngoogle_oauth_client = GoogleOAuth2(\n    os.getenv(\"GOOGLE_OAUTH_CLIENT_ID\", \"\"),\n    os.getenv(\"GOOGLE_OAUTH_CLIENT_SECRET\", \"\"),\n)\n\n\nclass UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):\n    reset_password_token_secret = SECRET\n    verification_token_secret = SECRET\n\n    async def on_after_register(self, user: User, request: Optional[Request] = None):\n        print(f\"User {user.id} has registered.\")\n\n    async def on_after_forgot_password(\n        self, user: User, token: str, request: Optional[Request] = None\n    ):\n        print(f\"User {user.id} has forgot their password. Reset token: {token}\")\n\n    async def on_after_request_verify(\n        self, user: User, token: str, request: Optional[Request] = None\n    ):\n        print(f\"Verification requested for user {user.id}. Verification token: {token}\")\n\n\nasync def get_user_manager(user_db: SQLAlchemyUserDatabase = Depends(get_user_db)):\n    yield UserManager(user_db)\n\n\nbearer_transport = BearerTransport(tokenUrl=\"auth/jwt/login\")\n\n\ndef get_jwt_strategy() -> JWTStrategy[models.UP, models.ID]:\n    return JWTStrategy(secret=SECRET, lifetime_seconds=3600)\n\n\nauth_backend = AuthenticationBackend(\n    name=\"jwt\",\n    transport=bearer_transport,\n    get_strategy=get_jwt_strategy,\n)\n\nfastapi_users = FastAPIUsers[User, uuid.UUID](get_user_manager, [auth_backend])\n\ncurrent_active_user = fastapi_users.current_user(active=True)\n
"},{"location":"configuration/oauth/#beanie_1","title":"Beanie","text":"

Open

requirements.txtmain.pyapp/app.pyapp/db.pyapp/schemas.pyapp/users.py
fastapi\nfastapi-users[beanie,oauth]\nuvicorn[standard]\n
import uvicorn\n\nif __name__ == \"__main__\":\n    uvicorn.run(\"app.app:app\", host=\"0.0.0.0\", log_level=\"info\")\n
from contextlib import asynccontextmanager\n\nfrom beanie import init_beanie\nfrom fastapi import Depends, FastAPI\n\nfrom app.db import User, db\nfrom app.schemas import UserCreate, UserRead, UserUpdate\nfrom app.users import (\n    SECRET,\n    auth_backend,\n    current_active_user,\n    fastapi_users,\n    google_oauth_client,\n)\n\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    await init_beanie(\n        database=db,\n        document_models=[\n            User,\n        ],\n    )\n    yield\n\n\napp = FastAPI(lifespan=lifespan)\n\napp.include_router(\n    fastapi_users.get_auth_router(auth_backend), prefix=\"/auth/jwt\", tags=[\"auth\"]\n)\napp.include_router(\n    fastapi_users.get_register_router(UserRead, UserCreate),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\napp.include_router(\n    fastapi_users.get_reset_password_router(),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\napp.include_router(\n    fastapi_users.get_verify_router(UserRead),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\napp.include_router(\n    fastapi_users.get_users_router(UserRead, UserUpdate),\n    prefix=\"/users\",\n    tags=[\"users\"],\n)\napp.include_router(\n    fastapi_users.get_oauth_router(google_oauth_client, auth_backend, SECRET),\n    prefix=\"/auth/google\",\n    tags=[\"auth\"],\n)\n\n\n@app.get(\"/authenticated-route\")\nasync def authenticated_route(user: User = Depends(current_active_user)):\n    return {\"message\": f\"Hello {user.email}!\"}\n
import motor.motor_asyncio\nfrom beanie import Document\nfrom fastapi_users.db import BaseOAuthAccount, BeanieBaseUser, BeanieUserDatabase\nfrom pydantic import Field\n\nDATABASE_URL = \"mongodb://localhost:27017\"\nclient = motor.motor_asyncio.AsyncIOMotorClient(\n    DATABASE_URL, uuidRepresentation=\"standard\"\n)\ndb = client[\"database_name\"]\n\n\nclass OAuthAccount(BaseOAuthAccount):\n    pass\n\n\nclass User(BeanieBaseUser, Document):\n    oauth_accounts: list[OAuthAccount] = Field(default_factory=list)\n\n\nasync def get_user_db():\n    yield BeanieUserDatabase(User, OAuthAccount)\n
from beanie import PydanticObjectId\nfrom fastapi_users import schemas\n\n\nclass UserRead(schemas.BaseUser[PydanticObjectId]):\n    pass\n\n\nclass UserCreate(schemas.BaseUserCreate):\n    pass\n\n\nclass UserUpdate(schemas.BaseUserUpdate):\n    pass\n
import os\nfrom typing import Optional\n\nfrom beanie import PydanticObjectId\nfrom fastapi import Depends, Request\nfrom fastapi_users import BaseUserManager, FastAPIUsers, models\nfrom fastapi_users.authentication import (\n    AuthenticationBackend,\n    BearerTransport,\n    JWTStrategy,\n)\nfrom fastapi_users.db import BeanieUserDatabase, ObjectIDIDMixin\nfrom httpx_oauth.clients.google import GoogleOAuth2\n\nfrom app.db import User, get_user_db\n\nSECRET = \"SECRET\"\n\ngoogle_oauth_client = GoogleOAuth2(\n    os.getenv(\"GOOGLE_OAUTH_CLIENT_ID\", \"\"),\n    os.getenv(\"GOOGLE_OAUTH_CLIENT_SECRET\", \"\"),\n)\n\n\nclass UserManager(ObjectIDIDMixin, BaseUserManager[User, PydanticObjectId]):\n    reset_password_token_secret = SECRET\n    verification_token_secret = SECRET\n\n    async def on_after_register(self, user: User, request: Optional[Request] = None):\n        print(f\"User {user.id} has registered.\")\n\n    async def on_after_forgot_password(\n        self, user: User, token: str, request: Optional[Request] = None\n    ):\n        print(f\"User {user.id} has forgot their password. Reset token: {token}\")\n\n    async def on_after_request_verify(\n        self, user: User, token: str, request: Optional[Request] = None\n    ):\n        print(f\"Verification requested for user {user.id}. Verification token: {token}\")\n\n\nasync def get_user_manager(user_db: BeanieUserDatabase = Depends(get_user_db)):\n    yield UserManager(user_db)\n\n\nbearer_transport = BearerTransport(tokenUrl=\"auth/jwt/login\")\n\n\ndef get_jwt_strategy() -> JWTStrategy[models.UP, models.ID]:\n    return JWTStrategy(secret=SECRET, lifetime_seconds=3600)\n\n\nauth_backend = AuthenticationBackend(\n    name=\"jwt\",\n    transport=bearer_transport,\n    get_strategy=get_jwt_strategy,\n)\n\nfastapi_users = FastAPIUsers[User, PydanticObjectId](get_user_manager, [auth_backend])\n\ncurrent_active_user = fastapi_users.current_user(active=True)\n
"},{"location":"configuration/overview/","title":"Overview","text":"

The schema below shows you how the library is structured and how each part fit together.

flowchart TB\n    FASTAPI_USERS{FastAPIUsers}\n    USER_MANAGER{UserManager}\n    USER_MODEL{User model}\n    DATABASE_DEPENDENCY[[get_user_db]]\n    USER_MANAGER_DEPENDENCY[[get_user_manager]]\n    CURRENT_USER[[current_user]]\n    subgraph SCHEMAS[Schemas]\n        USER[User]\n        USER_CREATE[UserCreate]\n        USER_UPDATE[UserUpdate]\n    end\n    subgraph DATABASE[Database adapters]\n        SQLALCHEMY[SQLAlchemy]\n        BEANIE[Beanie]\n    end\n    subgraph ROUTERS[Routers]\n        AUTH[[get_auth_router]]\n        OAUTH[[get_oauth_router]]\n        OAUTH_ASSOCIATE[[get_oauth_associate_router]]\n        REGISTER[[get_register_router]]\n        VERIFY[[get_verify_router]]\n        RESET[[get_reset_password_router]]\n        USERS[[get_users_router]]\n    end\n    subgraph AUTH_BACKENDS[Authentication]\n        subgraph TRANSPORTS[Transports]\n            COOKIE[CookieTransport]\n            BEARER[BearerTransport]\n        end\n        subgraph STRATEGIES[Strategies]\n            DB[DatabaseStrategy]\n            JWT[JWTStrategy]\n            REDIS[RedisStrategy]\n        end\n        AUTH_BACKEND{AuthenticationBackend}\n    end\n    DATABASE --> DATABASE_DEPENDENCY\n    USER_MODEL --> DATABASE_DEPENDENCY\n    DATABASE_DEPENDENCY --> USER_MANAGER\n\n    USER_MANAGER --> USER_MANAGER_DEPENDENCY\n    USER_MANAGER_DEPENDENCY --> FASTAPI_USERS\n\n    FASTAPI_USERS --> ROUTERS\n\n    TRANSPORTS --> AUTH_BACKEND\n    STRATEGIES --> AUTH_BACKEND\n\n    AUTH_BACKEND --> ROUTERS\n    AUTH_BACKEND --> FASTAPI_USERS\n\n    FASTAPI_USERS --> CURRENT_USER\n\n    SCHEMAS --> ROUTERS
"},{"location":"configuration/overview/#user-model-and-database-adapters","title":"User model and database adapters","text":"

FastAPI Users is compatible with various databases and ORM. To build the interface between those database tools and the library, we provide database adapters classes that you need to instantiate and configure.

\u27a1\ufe0f I'm using SQLAlchemy

\u27a1\ufe0f I'm using Beanie

"},{"location":"configuration/overview/#authentication-backends","title":"Authentication backends","text":"

Authentication backends define the way users sessions are managed in your app, like access tokens or cookies.

They are composed of two parts: a transport, which is how the token will be carried over the requests (e.g. cookies, headers...) and a strategy, which is how the token will be generated and secured (e.g. a JWT, a token in database...).

\u27a1\ufe0f Configure the authentication backends

"},{"location":"configuration/overview/#usermanager","title":"UserManager","text":"

The UserManager object bears most of the logic of FastAPI Users: registration, verification, password reset... We provide a BaseUserManager with this common logic; which you should overload to define how to validate passwords or handle events.

This UserManager object should be provided through a FastAPI dependency, get_user_manager.

\u27a1\ufe0f Configure UserManager

"},{"location":"configuration/overview/#schemas","title":"Schemas","text":"

FastAPI is heavily using Pydantic models to validate request payloads and serialize responses. FastAPI Users is no exception and will expect you to provide Pydantic schemas representing a user when it's read, created and updated.

\u27a1\ufe0f Configure schemas

"},{"location":"configuration/overview/#fastapiusers-and-routers","title":"FastAPIUsers and routers","text":"

Finally, FastAPIUsers object is the main class from which you'll be able to generate routers for classic routes like registration or login, but also get the current_user dependency factory to inject the authenticated user in your own routes.

\u27a1\ufe0f Configure FastAPIUsers and routers

"},{"location":"configuration/password-hash/","title":"Password hash","text":"

By default, FastAPI Users will use the Argon2 algorithm to hash and salt passwords before storing them in the database, with backwards-compatibility with Bcrypt.

The implementation is provided by pwdlib, a modern password hashing wrapper.

"},{"location":"configuration/password-hash/#customize-passwordhash","title":"Customize PasswordHash","text":"

If you need to tune the algorithms used or their settings, you can customize the PasswordHash object of pwdlib.

For this, you'll need to instantiate the PasswordHelper class and pass it your PasswordHash. The example below shows you how you can create a PasswordHash to only support the Argon2 algorithm.

from fastapi_users.password import PasswordHelper\nfrom pwdlib import PasswordHash, exceptions\nfrom pwdlib.hashers.argon2 import Argon2Hasher\n\npassword_hash = PasswordHash((\n    Argon2Hasher(),\n))\npassword_helper = PasswordHelper(password_hash)\n

Finally, pass the password_helper variable while instantiating your UserManager:

async def get_user_manager(user_db=Depends(get_user_db)):\n    yield UserManager(user_db, password_helper)\n

Password hashes are automatically upgraded

FastAPI Users takes care of upgrading the password hash to a more recent algorithm when needed.

Typically, when a user logs in, we'll check if the password hash algorithm is deprecated.

If it is, we take the opportunity of having the password in plain-text at hand (since the user just logged in!) to hash it with a better algorithm and update it in database.

"},{"location":"configuration/password-hash/#full-customization","title":"Full customization","text":"

If you don't wish to use pwdlib at all \u2013 which we don't recommend unless you're absolutely sure of what you're doing \u2014 you can implement your own PasswordHelper class as long as it implements the PasswordHelperProtocol and its methods.

from typing import Tuple\n\nfrom fastapi_users.password import PasswordHelperProtocol\n\nclass PasswordHelper(PasswordHelperProtocol):\n    def verify_and_update(\n        self, plain_password: str, hashed_password: str\n    ) -> Tuple[bool, str]:\n        ...\n\n    def hash(self, password: str) -> str:\n        ...\n\n    def generate(self) -> str:\n        ...\n
"},{"location":"configuration/schemas/","title":"Schemas","text":"

FastAPI is heavily using Pydantic models to validate request payloads and serialize responses. FastAPI Users is no exception and will expect you to provide Pydantic schemas representing a user when it's read, created and updated.

It's different from your User model, which is an object that actually interacts with the database. Those schemas on the other hand are here to validate data and correctly serialize it in the API.

FastAPI Users provides a base structure to cover its needs. It is structured like this:

  • id (ID) \u2013 Unique identifier of the user. It matches the type of your ID, like UUID or integer.
  • email (str) \u2013 Email of the user. Validated by email-validator.
  • is_active (bool) \u2013 Whether or not the user is active. If not, login and forgot password requests will be denied. Defaults to True.
  • is_verified (bool) \u2013 Whether or not the user is verified. Optional but helpful with the verify router logic. Defaults to False.
  • is_superuser (bool) \u2013 Whether or not the user is a superuser. Useful to implement administration logic. Defaults to False.
"},{"location":"configuration/schemas/#define-your-schemas","title":"Define your schemas","text":"

There are four Pydantic models variations provided as mixins:

  • BaseUser, which provides the basic fields and validation;
  • BaseCreateUser, dedicated to user registration, which consists of compulsory email and password fields;
  • BaseUpdateUser, dedicated to user profile update, which adds an optional password field;

You should define each of those variations, inheriting from each mixin:

import uuid\n\nfrom fastapi_users import schemas\n\n\nclass UserRead(schemas.BaseUser[uuid.UUID]):\n    pass\n\n\nclass UserCreate(schemas.BaseUserCreate):\n    pass\n\n\nclass UserUpdate(schemas.BaseUserUpdate):\n    pass\n

Typing: ID generic type is expected

You can see that we define a generic type when extending the BaseUser class. It should correspond to the type of ID you use on your model. Here, we chose UUID, but it can be anything, like an integer or a MongoDB ObjectID.

"},{"location":"configuration/schemas/#adding-your-own-fields","title":"Adding your own fields","text":"

You can of course add your own properties there to fit to your needs. In the example below, we add a required string property, first_name, and an optional date property, birthdate.

import datetime\nimport uuid\n\nfrom fastapi_users import schemas\n\n\nclass UserRead(schemas.BaseUser[uuid.UUID]):\n    first_name: str\n    birthdate: Optional[datetime.date]\n\n\nclass UserCreate(schemas.BaseUserCreate):\n    first_name: str\n    birthdate: Optional[datetime.date]\n\n\nclass UserUpdate(schemas.BaseUserUpdate):\n    first_name: Optional[str]\n    birthdate: Optional[datetime.date]\n

Make sure to mirror this in your database model

The User model you defined earlier for your specific database will be the central object that will actually store the data. Therefore, you need to define the very same fields in it so the data can be actually stored.

"},{"location":"configuration/user-manager/","title":"UserManager","text":"

The UserManager class is the core logic of FastAPI Users. We provide the BaseUserManager class which you should extend to set some parameters and define logic, for example when a user just registered or forgot its password.

It's designed to be easily extensible and customizable so that you can integrate your very own logic.

"},{"location":"configuration/user-manager/#create-your-usermanager-class","title":"Create your UserManager class","text":"

You should define your own version of the UserManager class to set various parameters.

import uuid\nfrom typing import Optional\n\nfrom fastapi import Depends, Request\nfrom fastapi_users import BaseUserManager, UUIDIDMixin\n\nfrom .db import User, get_user_db\n\nSECRET = \"SECRET\"\n\n\nclass UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):\n    reset_password_token_secret = SECRET\n    verification_token_secret = SECRET\n\n    async def on_after_register(self, user: User, request: Optional[Request] = None):\n        print(f\"User {user.id} has registered.\")\n\n    async def on_after_forgot_password(\n        self, user: User, token: str, request: Optional[Request] = None\n    ):\n        print(f\"User {user.id} has forgot their password. Reset token: {token}\")\n\n    async def on_after_request_verify(\n        self, user: User, token: str, request: Optional[Request] = None\n    ):\n        print(f\"Verification requested for user {user.id}. Verification token: {token}\")\n\n\nasync def get_user_manager(user_db=Depends(get_user_db)):\n    yield UserManager(user_db)\n

As you can see, you have to define here various attributes and methods. You can find the complete list of those below.

Typing: User and ID generic types are expected

You can see that we define two generic types when extending the base class:

  • User, which is the user model we defined in the database part
  • The ID, which should correspond to the type of ID you use on your model. Here, we chose UUID, but it can be anything, like an integer or a MongoDB ObjectID.

It'll help you to have good type-checking and auto-completion when implementing the custom methods.

"},{"location":"configuration/user-manager/#the-id-parser-mixin","title":"The ID parser mixin","text":"

Since the user ID is fully generic, we need a way to parse it reliably when it'll come from API requests, typically as URL path attributes.

That's why we added the UUIDIDMixin in the example above. It implements the parse_id method, ensuring UUID are valid and correctly parsed.

Of course, it's important that this logic matches the type of your ID. To help you with this, we provide mixins for the most common cases:

  • UUIDIDMixin, for UUID ID.
  • IntegerIDMixin, for integer ID.
  • ObjectIDIDMixin (provided by fastapi_users_db_beanie), for MongoDB ObjectID.

Inheritance order matters

Notice in your example that the mixin comes first in our UserManager inheritance. Because of the Method-Resolution-Order (MRO) of Python, the left-most element takes precedence.

If you need another type of ID, you can simply overload the parse_id method on your UserManager class:

from fastapi_users import BaseUserManager, InvalidID\n\n\nclass UserManager(BaseUserManager[User, MyCustomID]):\n    def parse_id(self, value: Any) -> MyCustomID:\n        try:\n            return MyCustomID(value)\n        except ValueError as e:\n            raise InvalidID() from e  # (1)!\n
  1. If the ID can't be parsed into the desired type, you'll need to raise an InvalidID exception.
"},{"location":"configuration/user-manager/#create-get_user_manager-dependency","title":"Create get_user_manager dependency","text":"

The UserManager class will be injected at runtime using a FastAPI dependency. This way, you can run it in a database session or swap it with a mock during testing.

import uuid\nfrom typing import Optional\n\nfrom fastapi import Depends, Request\nfrom fastapi_users import BaseUserManager, UUIDIDMixin\n\nfrom .db import User, get_user_db\n\nSECRET = \"SECRET\"\n\n\nclass UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):\n    reset_password_token_secret = SECRET\n    verification_token_secret = SECRET\n\n    async def on_after_register(self, user: User, request: Optional[Request] = None):\n        print(f\"User {user.id} has registered.\")\n\n    async def on_after_forgot_password(\n        self, user: User, token: str, request: Optional[Request] = None\n    ):\n        print(f\"User {user.id} has forgot their password. Reset token: {token}\")\n\n    async def on_after_request_verify(\n        self, user: User, token: str, request: Optional[Request] = None\n    ):\n        print(f\"Verification requested for user {user.id}. Verification token: {token}\")\n\n\nasync def get_user_manager(user_db=Depends(get_user_db)):\n    yield UserManager(user_db)\n

Notice that we use the get_user_db dependency we defined earlier to inject the database instance.

"},{"location":"configuration/user-manager/#customize-attributes-and-methods","title":"Customize attributes and methods","text":""},{"location":"configuration/user-manager/#attributes","title":"Attributes","text":"
  • reset_password_token_secret: Secret to encode reset password token. Use a strong passphrase and keep it secure.
  • reset_password_token_lifetime_seconds: Lifetime of reset password token. Defaults to 3600.
  • reset_password_token_audience: JWT audience of reset password token. Defaults to fastapi-users:reset.
  • verification_token_secret: Secret to encode verification token. Use a strong passphrase and keep it secure.
  • verification_token_lifetime_seconds: Lifetime of verification token. Defaults to 3600.
  • verification_token_audience: JWT audience of verification token. Defaults to fastapi-users:verify.
"},{"location":"configuration/user-manager/#methods","title":"Methods","text":""},{"location":"configuration/user-manager/#validate_password","title":"validate_password","text":"

Validate a password.

Arguments

  • password (str): the password to validate.
  • user (Union[UserCreate, User]): user model which we are currently validating the password. Useful if you want to check that the password doesn't contain the name or the birthdate of the user for example.

Output

This function should return None if the password is valid or raise InvalidPasswordException if not. This exception expects an argument reason telling why the password is invalid. It'll be part of the error response.

Example

from fastapi_users import BaseUserManager, InvalidPasswordException, UUIDIDMixin\n\n\nclass UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):\n    # ...\n    async def validate_password(\n        self,\n        password: str,\n        user: Union[UserCreate, User],\n    ) -> None:\n        if len(password) < 8:\n            raise InvalidPasswordException(\n                reason=\"Password should be at least 8 characters\"\n            )\n        if user.email in password:\n            raise InvalidPasswordException(\n                reason=\"Password should not contain e-mail\"\n            )\n
"},{"location":"configuration/user-manager/#on_after_register","title":"on_after_register","text":"

Perform logic after successful user registration.

Typically, you'll want to send a welcome e-mail or add it to your marketing analytics pipeline.

Arguments

  • user (User): the registered user.
  • request (Optional[Request]): optional FastAPI request object that triggered the operation. Defaults to None.

Example

from fastapi_users import BaseUserManager, UUIDIDMixin\n\n\nclass UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):\n    # ...\n    async def on_after_register(self, user: User, request: Optional[Request] = None):\n        print(f\"User {user.id} has registered.\")\n
"},{"location":"configuration/user-manager/#on_after_update","title":"on_after_update","text":"

Perform logic after successful user update.

It may be useful, for example, if you wish to update your user in a data analytics or customer success platform.

Arguments

  • user (User): the updated user.
  • update_dict (Dict[str, Any]): dictionary with the updated user fields.
  • request (Optional[Request]): optional FastAPI request object that triggered the operation. Defaults to None.

Example

from fastapi_users import BaseUserManager, UUIDIDMixin\n\n\nclass UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):\n    # ...\n    async def on_after_update(\n        self,\n        user: User,\n        update_dict: Dict[str, Any],\n        request: Optional[Request] = None,\n    ):\n        print(f\"User {user.id} has been updated with {update_dict}.\")\n
"},{"location":"configuration/user-manager/#on_after_login","title":"on_after_login","text":"

Perform logic after a successful user login.

It may be useful for custom logic or processes triggered by new logins, for example a daily login reward or for analytics.

Arguments

  • user (User): the updated user.
  • request (Optional[Request]): optional FastAPI request object that triggered the operation. Defaults to None.
  • response (Optional[Response]): Optional response built by the transport. Defaults to None.

Example

from fastapi_users import BaseUserManager, UUIDIDMixin\n\n\nclass UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):\n    # ...\n    async def on_after_login(\n        self,\n        user: User,\n        request: Optional[Request] = None,\n        response: Optional[Response] = None,\n    ):\n        print(f\"User {user.id} logged in.\")\n
"},{"location":"configuration/user-manager/#on_after_request_verify","title":"on_after_request_verify","text":"

Perform logic after successful verification request.

Typically, you'll want to send an e-mail with the link (and the token) that allows the user to verify their e-mail.

Arguments

  • user (User): the user to verify.
  • token (str): the verification token.
  • request (Optional[Request]): optional FastAPI request object that triggered the operation. Defaults to None.

Example

from fastapi_users import BaseUserManager, UUIDIDMixin\n\n\nclass UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):\n    # ...\n    async def on_after_request_verify(\n        self, user: User, token: str, request: Optional[Request] = None\n    ):\n        print(f\"Verification requested for user {user.id}. Verification token: {token}\")\n
"},{"location":"configuration/user-manager/#on_after_verify","title":"on_after_verify","text":"

Perform logic after successful user verification.

This may be useful if you wish to send another e-mail or store this information in a data analytics or customer success platform.

Arguments

  • user (User): the verified user.
  • request (Optional[Request]): optional FastAPI request object that triggered the operation. Defaults to None.

Example

from fastapi_users import BaseUserManager, UUIDIDMixin\n\n\nclass UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):\n    # ...\n    async def on_after_verify(\n        self, user: User, request: Optional[Request] = None\n    ):\n        print(f\"User {user.id} has been verified\")\n
"},{"location":"configuration/user-manager/#on_after_forgot_password","title":"on_after_forgot_password","text":"

Perform logic after successful forgot password request.

Typically, you'll want to send an e-mail with the link (and the token) that allows the user to reset their password.

Arguments

  • user (User): the user that forgot its password.
  • token (str): the forgot password token
  • request (Optional[Request]): optional FastAPI request object that triggered the operation. Defaults to None.

Example

from fastapi_users import BaseUserManager, UUIDIDMixin\n\n\nclass UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):\n    # ...\n    async def on_after_forgot_password(\n        self, user: User, token: str, request: Optional[Request] = None\n    ):\n        print(f\"User {user.id} has forgot their password. Reset token: {token}\")\n
"},{"location":"configuration/user-manager/#on_after_reset_password","title":"on_after_reset_password","text":"

Perform logic after successful password reset.

For example, you may want to send an e-mail to the concerned user to warn him that their password has been changed and that they should take action if they think they have been hacked.

Arguments

  • user (User): the user that reset its password.
  • request (Optional[Request]): optional FastAPI request object that triggered the operation. Defaults to None.

Example

from fastapi_users import BaseUserManager, UUIDIDMixin\n\n\nclass UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):\n    # ...\n    async def on_after_reset_password(self, user: User, request: Optional[Request] = None):\n        print(f\"User {user.id} has reset their password.\")\n
"},{"location":"configuration/user-manager/#on_before_delete","title":"on_before_delete","text":"

Perform logic before user delete.

For example, you may want to valide user resource integrity to see if any related user resource need to be marked inactive, or delete them recursively.

Arguments

  • user (User): the user to be deleted.
  • request (Optional[Request]): optional FastAPI request object that triggered the operation. Defaults to None.

Example

from fastapi_users import BaseUserManager, UUIDIDMixin\n\n\nclass UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):\n    # ...\n    async def on_before_delete(self, user: User, request: Optional[Request] = None):\n        print(f\"User {user.id} is going to be deleted\")\n
"},{"location":"configuration/user-manager/#on_after_delete","title":"on_after_delete","text":"

Perform logic after user delete.

For example, you may want to send an email to the administrator about the event.

Arguments

  • user (User): the user to be deleted.
  • request (Optional[Request]): optional FastAPI request object that triggered the operation. Defaults to None.

Example

from fastapi_users import BaseUserManager, UUIDIDMixin\n\n\nclass UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):\n    # ...\n    async def on_after_delete(self, user: User, request: Optional[Request] = None):\n        print(f\"User {user.id} is successfully deleted\")\n
"},{"location":"configuration/authentication/","title":"Authentication","text":"

FastAPI Users allows you to plug in several authentication methods.

"},{"location":"configuration/authentication/#how-it-works","title":"How it works?","text":"

You can have several authentication methods, e.g. a cookie authentication for browser-based queries and a JWT token authentication for pure API queries.

When checking authentication, each method is run one after the other. The first method yielding a user wins. If no method yields a user, an HTTPException is raised.

For each backend, you'll be able to add a router with the corresponding /login and /logout. More on this in the routers documentation.

"},{"location":"configuration/authentication/#transport-strategy-authentication-backend","title":"Transport + Strategy = Authentication backend","text":"

An authentication backend is composed of two parts:

"},{"location":"configuration/authentication/#transport","title":"Transport","text":"

It manages how the token will be carried over the request. We currently provide two methods:

"},{"location":"configuration/authentication/#bearer","title":"Bearer","text":"

The token will be sent through an Authorization: Bearer header.

Pros and cons

  • \u2705 Easy to read and set in every requests.
  • \u274c Needs to be stored manually somewhere in the client.

\u27a1\ufe0f Use it if you want to implement a mobile application or a pure REST API.

"},{"location":"configuration/authentication/#cookie","title":"Cookie","text":"

The token will be sent through a cookie.

Pros and cons

  • \u2705 Automatically stored and sent securely by web browsers in every requests.
  • \u2705 Automatically removed at expiration by web browsers.
  • \u274c Needs a CSRF protection for maximum security.
  • \u274c Harder to work with outside a browser, like a mobile app or a server.

\u27a1\ufe0f Use it if you want to implement a web frontend.

"},{"location":"configuration/authentication/#strategy","title":"Strategy","text":"

It manages how the token is generated and secured. We currently provide three methods:

"},{"location":"configuration/authentication/#jwt","title":"JWT","text":"

The token is self-contained in a JSON Web Token.

Pros and cons

  • \u2705 Self-contained: it doesn't need to be stored in a database.
  • \u274c Can't be invalidated on the server-side: it's valid until it expires.

\u27a1\ufe0f Use it if you want to get up-and-running quickly.

"},{"location":"configuration/authentication/#database","title":"Database","text":"

The token is stored in a table (or collection) in your database.

Pros and cons

  • \u2705 Secure and performant.
  • \u2705 Tokens can be invalidated server-side by removing them from the database.
  • \u2705 Highly customizable: add your own fields, create an API to retrieve the active sessions of your users, etc.
  • \u274c Configuration is a bit more complex.

\u27a1\ufe0f Use it if you want maximum flexibility in your token management.

"},{"location":"configuration/authentication/#redis","title":"Redis","text":"

The token is stored in a Redis key-store.

Pros and cons

  • \u2705 Secure and performant.
  • \u2705 Tokens can be invalidated server-side by removing them from Redis.
  • \u274c A Redis server is needed.

\u27a1\ufe0f Use it if you want maximum performance while being able to invalidate tokens.

"},{"location":"configuration/authentication/backend/","title":"Create a backend","text":"

As we said, a backend is the combination of a transport and a strategy. That way, you can create a complete strategy exactly fitting your needs.

For this, you have to use the AuthenticationBackend class.

from fastapi_users.authentication import AuthenticationBackend, BearerTransport, JWTStrategy\n\nSECRET = \"SECRET\"\n\nbearer_transport = BearerTransport(tokenUrl=\"auth/jwt/login\")\n\ndef get_jwt_strategy() -> JWTStrategy:\n    return JWTStrategy(secret=SECRET, lifetime_seconds=3600)\n\nauth_backend = AuthenticationBackend(\n    name=\"jwt\",\n    transport=bearer_transport,\n    get_strategy=get_jwt_strategy,\n)\n

As you can see, instantiation is quite simple. It accepts the following arguments:

  • name (str): Name of the backend. Each backend should have a unique name.
  • transport (Transport): An instance of a Transport class.
  • get_strategy (Callable[..., Strategy]): A dependency callable returning an instance of a Strategy class.
"},{"location":"configuration/authentication/backend/#next-steps","title":"Next steps","text":"

You can have as many authentication backends as you wish. You'll then have to pass those backends to your FastAPIUsers instance and generate an auth router for each one of them.

"},{"location":"configuration/authentication/strategies/database/","title":"Database","text":"

The most natural way for storing tokens is of course the very same database you're using for your application. In this strategy, we set up a table (or collection) for storing those tokens with the associated user id. On each request, we try to retrieve this token from the database to get the corresponding user id.

"},{"location":"configuration/authentication/strategies/database/#configuration","title":"Configuration","text":"

The configuration of this strategy is a bit more complex than the others as it requires you to configure models and a database adapter, exactly like we did for users.

"},{"location":"configuration/authentication/strategies/database/#database-adapters","title":"Database adapters","text":"

An access token will be structured like this in your database:

  • token (str) \u2013 Unique identifier of the token. It's generated automatically upon login by the strategy.
  • user_id (ID) \u2013 User id. of the user associated to this token.
  • created_at (datetime) \u2013 Date and time of creation of the token. It's used to determine if the token is expired or not.

We are providing a base model with those fields for each database we are supporting.

"},{"location":"configuration/authentication/strategies/database/#sqlalchemy","title":"SQLAlchemy","text":"

We'll expand from the basic SQLAlchemy configuration.

from collections.abc import AsyncGenerator\n\nfrom fastapi import Depends\nfrom fastapi_users.db import SQLAlchemyBaseUserTableUUID, SQLAlchemyUserDatabase\nfrom fastapi_users_db_sqlalchemy.access_token import (\n    SQLAlchemyAccessTokenDatabase,\n    SQLAlchemyBaseAccessTokenTableUUID,\n)\nfrom sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine\nfrom sqlalchemy.orm import DeclarativeBase\n\nDATABASE_URL = \"sqlite+aiosqlite:///./test.db\"\n\n\nclass Base(DeclarativeBase):\n    pass\n\n\nclass User(SQLAlchemyBaseUserTableUUID, Base):\n    pass\n\n\nclass AccessToken(SQLAlchemyBaseAccessTokenTableUUID, Base):  # (1)!\n    pass\n\n\nengine = create_async_engine(DATABASE_URL)\nasync_session_maker = async_sessionmaker(engine, expire_on_commit=False)\n\n\nasync def create_db_and_tables():\n    async with engine.begin() as conn:\n        await conn.run_sync(Base.metadata.create_all)\n\n\nasync def get_async_session() -> AsyncGenerator[AsyncSession, None]:\n    async with async_session_maker() as session:\n        yield session\n\n\nasync def get_user_db(session: AsyncSession = Depends(get_async_session)):\n    yield SQLAlchemyUserDatabase(session, User)\n\n\nasync def get_access_token_db(\n    session: AsyncSession = Depends(get_async_session),\n):  # (2)!\n    yield SQLAlchemyAccessTokenDatabase(session, AccessToken)\n
  1. We define an AccessToken ORM model inheriting from SQLAlchemyBaseAccessTokenTableUUID.

  2. We define a dependency to instantiate the SQLAlchemyAccessTokenDatabase class. Just like the user database adapter, it expects a fresh SQLAlchemy session and the AccessToken model class we defined above.

user_id foreign key is defined as UUID

By default, we use UUID as a primary key ID for your user, so we follow the same convention to define the foreign key pointing to the user.

If you want to use another type, like an auto-incremented integer, you can use SQLAlchemyBaseAccessTokenTable as base class and define your own user_id column.

class AccessToken(SQLAlchemyBaseAccessTokenTable[int], Base):\n    @declared_attr\n    def user_id(cls) -> Mapped[int]:\n        return mapped_column(Integer, ForeignKey(\"user.id\", ondelete=\"cascade\"), nullable=False)\n

Notice that SQLAlchemyBaseAccessTokenTable expects a generic type to define the actual type of ID you use.

"},{"location":"configuration/authentication/strategies/database/#beanie","title":"Beanie","text":"

We'll expand from the basic Beanie configuration.

import motor.motor_asyncio\nfrom beanie import Document\nfrom fastapi_users.db import BeanieBaseUser, BeanieUserDatabase\nfrom fastapi_users_db_beanie.access_token import (\n    BeanieAccessTokenDatabase,\n    BeanieBaseAccessToken,\n)\n\nDATABASE_URL = \"mongodb://localhost:27017\"\nclient = motor.motor_asyncio.AsyncIOMotorClient(\n    DATABASE_URL, uuidRepresentation=\"standard\"\n)\ndb = client[\"database_name\"]\n\n\nclass User(BeanieBaseUser, Document):\n    pass\n\n\nclass AccessToken(BeanieBaseAccessToken, Document):  # (1)!\n    pass\n\n\nasync def get_user_db():\n    yield BeanieUserDatabase(User)\n\n\nasync def get_access_token_db():  # (2)!\n    yield BeanieAccessTokenDatabase(AccessToken)\n
  1. We define an AccessToken ODM model inheriting from BeanieBaseAccessToken. Notice that we set a generic type to define the type of the user_id reference. By default, it's a standard MongoDB ObjectID.

  2. We define a dependency to instantiate the BeanieAccessTokenDatabase class. Just like the user database adapter, it expects the AccessToken model class we defined above.

Don't forget to add the AccessToken ODM model to the document_models array in your Beanie initialization, just like you did with the User model!

Info

If you want to add your own custom settings to your AccessToken document model - like changing the collection name - don't forget to let your inner Settings class inherit the pre-defined settings from BeanieBaseAccessToken like this: Settings(BeanieBaseAccessToken.Settings): # ...! See Beanie's documentation on Settings for details.

"},{"location":"configuration/authentication/strategies/database/#strategy","title":"Strategy","text":"
import uuid\n\nfrom fastapi import Depends\nfrom fastapi_users.authentication.strategy.db import AccessTokenDatabase, DatabaseStrategy\n\nfrom .db import AccessToken, User\n\n\ndef get_database_strategy(\n    access_token_db: AccessTokenDatabase[AccessToken] = Depends(get_access_token_db),\n) -> DatabaseStrategy:\n    return DatabaseStrategy(access_token_db, lifetime_seconds=3600)\n

As you can see, instantiation is quite simple. It accepts the following arguments:

  • database (AccessTokenDatabase): A database adapter instance for AccessToken table, like we defined above.
  • lifetime_seconds (int): The lifetime of the token in seconds.

Why it's inside a function?

To allow strategies to be instantiated dynamically with other dependencies, they have to be provided as a callable to the authentication backend.

As you can see here, this pattern allows us to dynamically inject a connection to the database.

"},{"location":"configuration/authentication/strategies/database/#logout","title":"Logout","text":"

On logout, this strategy will delete the token from the database.

"},{"location":"configuration/authentication/strategies/jwt/","title":"JWT","text":"

JSON Web Token (JWT) is an internet standard for creating access tokens based on JSON. They don't need to be stored in a database: the data is self-contained inside and cryptographically signed.

"},{"location":"configuration/authentication/strategies/jwt/#configuration","title":"Configuration","text":"
from fastapi_users.authentication import JWTStrategy\n\nSECRET = \"SECRET\"\n\ndef get_jwt_strategy() -> JWTStrategy:\n    return JWTStrategy(secret=SECRET, lifetime_seconds=3600)\n

As you can see, instantiation is quite simple. It accepts the following arguments:

  • secret (Union[str, pydantic.SecretStr]): A constant secret which is used to encode the token. Use a strong passphrase and keep it secure.
  • lifetime_seconds (Optional[int]): The lifetime of the token in seconds. Can be set to None but in this case the token will be valid forever; which may raise serious security concerns.
  • token_audience (Optional[List[str]]): A list of valid audiences for the JWT token. Defaults to [\"fastapi-users:auth\"].
  • algorithm (Optional[str]): The JWT encryption algorithm. See RFC 7519, section 8. Defaults to \"HS256\".
  • public_key (Optional[Union[str, pydantic.SecretStr]]): If the JWT encryption algorithm requires a key pair instead of a simple secret, the key to decrypt the JWT may be provided here. The secret parameter will always be used to encrypt the JWT.

Why it's inside a function?

To allow strategies to be instantiated dynamically with other dependencies, they have to be provided as a callable to the authentication backend.

For JWTStrategy, since it doesn't require dependencies, it can be as simple as the function above.

"},{"location":"configuration/authentication/strategies/jwt/#rs256-example","title":"RS256 example","text":"
from fastapi_users.authentication import JWTStrategy\n\nPUBLIC_KEY = \"\"\"-----BEGIN PUBLIC KEY-----\n# Your RSA public key in PEM format goes here\n-----END PUBLIC KEY-----\"\"\"\n\nPRIVATE_KEY = \"\"\"-----BEGIN RSA PRIVATE KEY-----\n# Your RSA private key in PEM format goes here\n-----END RSA PRIVATE KEY-----\"\"\"\n\ndef get_jwt_strategy() -> JWTStrategy:\n    return JWTStrategy(\n        secret=PRIVATE_KEY, \n        lifetime_seconds=3600,\n        algorithm=\"RS256\",\n        public_key=PUBLIC_KEY,\n    )\n
"},{"location":"configuration/authentication/strategies/jwt/#logout","title":"Logout","text":"

On logout, this strategy won't do anything. Indeed, a JWT can't be invalidated on the server-side: it's valid until it expires.

"},{"location":"configuration/authentication/strategies/redis/","title":"Redis","text":"

Redis is an ultra-fast key-store database. As such, it's a good candidate for token management. In this strategy, a token is generated and associated with the user id. in the database. On each request, we try to retrieve this token from Redis to get the corresponding user id.

"},{"location":"configuration/authentication/strategies/redis/#installation","title":"Installation","text":"

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

pip install 'fastapi-users[redis]'\n
"},{"location":"configuration/authentication/strategies/redis/#configuration","title":"Configuration","text":"
import redis.asyncio\nfrom fastapi_users.authentication import RedisStrategy\n\nredis = redis.asyncio.from_url(\"redis://localhost:6379\", decode_responses=True)\n\ndef get_redis_strategy() -> RedisStrategy:\n    return RedisStrategy(redis, lifetime_seconds=3600)\n

As you can see, instantiation is quite simple. It accepts the following arguments:

  • redis (redis.asyncio.Redis): An instance of redis.asyncio.Redis. Note that the decode_responses flag set to True is necessary.
  • lifetime_seconds (Optional[int]): The lifetime of the token in seconds. Defaults to None, which means the token doesn't expire.
  • key_prefix (str): The prefix used to set the key in the Redis stored. Defaults to fastapi_users_token:.

Why it's inside a function?

To allow strategies to be instantiated dynamically with other dependencies, they have to be provided as a callable to the authentication backend.

"},{"location":"configuration/authentication/strategies/redis/#logout","title":"Logout","text":"

On logout, this strategy will delete the token from the Redis store.

"},{"location":"configuration/authentication/transports/bearer/","title":"Bearer","text":"

With this transport, the token is expected inside the Authorization header of the HTTP request with the Bearer scheme. It's particularly suited for pure API interaction or mobile apps.

"},{"location":"configuration/authentication/transports/bearer/#configuration","title":"Configuration","text":"
from fastapi_users.authentication import BearerTransport\n\nbearer_transport = BearerTransport(tokenUrl=\"auth/jwt/login\")\n

As you can see, instantiation is quite simple. It accepts the following arguments:

  • tokenUrl (str): The exact path of your login endpoint. It'll allow the interactive documentation to automatically discover it and get a working Authorize button. In most cases, you'll probably need a relative path, not absolute. You can read more details about this in the FastAPI documentation.
"},{"location":"configuration/authentication/transports/bearer/#login","title":"Login","text":"

This method will return the in the following form upon successful login:

200 OK

{\n    \"access_token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiOTIyMWZmYzktNjQwZi00MzcyLTg2ZDMtY2U2NDJjYmE1NjAzIiwiYXVkIjoiZmFzdGFwaS11c2VyczphdXRoIiwiZXhwIjoxNTcxNTA0MTkzfQ.M10bjOe45I5Ncu_uXvOmVV8QxnL-nZfcH96U90JaocI\",\n    \"token_type\": \"bearer\"\n}\n

Check documentation about login route.

"},{"location":"configuration/authentication/transports/bearer/#logout","title":"Logout","text":"

204 No content

"},{"location":"configuration/authentication/transports/bearer/#authentication","title":"Authentication","text":"

This method expects that you provide a Bearer authentication with a valid token corresponding to your strategy.

curl http://localhost:9000/protected-route -H'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiOTIyMWZmYzktNjQwZi00MzcyLTg2ZDMtY2U2NDJjYmE1NjAzIiwiYXVkIjoiZmFzdGFwaS11c2VyczphdXRoIiwiZXhwIjoxNTcxNTA0MTkzfQ.M10bjOe45I5Ncu_uXvOmVV8QxnL-nZfcH96U90JaocI'\n
"},{"location":"configuration/authentication/transports/cookie/","title":"Cookie","text":"

Cookies are an easy way to store stateful information into the user browser. Thus, it is more useful for browser-based navigation (e.g. a front-end app making API requests) rather than pure API interaction.

"},{"location":"configuration/authentication/transports/cookie/#configuration","title":"Configuration","text":"
from fastapi_users.authentication import CookieTransport\n\ncookie_transport = CookieTransport(cookie_max_age=3600)\n

As you can see, instantiation is quite simple. It accepts the following arguments:

  • cookie_name (fastapiusersauth): Name of the cookie.
  • cookie_max_age (Optional[int]): The lifetime of the cookie in seconds. None by default, which means it's a session cookie.
  • cookie_path (/): Cookie path.
  • cookie_domain (None): Cookie domain.
  • cookie_secure (True): Whether to only send the cookie to the server via SSL request.
  • cookie_httponly (True): Whether to prevent access to the cookie via JavaScript.
  • cookie_samesite (lax): A string that specifies the samesite strategy for the cookie. Valid values are lax, strict and none. Defaults to lax.
"},{"location":"configuration/authentication/transports/cookie/#login","title":"Login","text":"

This method will return a response with a valid set-cookie header upon successful login:

204 No content

Check documentation about login route.

"},{"location":"configuration/authentication/transports/cookie/#logout","title":"Logout","text":"

This method will remove the authentication cookie:

204 No content

Check documentation about logout route.

"},{"location":"configuration/authentication/transports/cookie/#authentication","title":"Authentication","text":"

This method expects that you provide a valid cookie in the headers.

"},{"location":"configuration/databases/beanie/","title":"Beanie","text":"

FastAPI Users provides the necessary tools to work with MongoDB databases using the Beanie ODM.

"},{"location":"configuration/databases/beanie/#setup-database-connection-and-collection","title":"Setup database connection and collection","text":"

The first thing to do is to create a MongoDB connection using mongodb/motor (automatically installed with Beanie).

import motor.motor_asyncio\nfrom beanie import Document\nfrom fastapi_users.db import BeanieBaseUser, BeanieUserDatabase\n\nDATABASE_URL = \"mongodb://localhost:27017\"\nclient = motor.motor_asyncio.AsyncIOMotorClient(\n    DATABASE_URL, uuidRepresentation=\"standard\"\n)\ndb = client[\"database_name\"]\n\n\nclass User(BeanieBaseUser, Document):\n    pass\n\n\nasync def get_user_db():\n    yield BeanieUserDatabase(User)\n

You can choose any name for the database.

"},{"location":"configuration/databases/beanie/#create-the-user-model","title":"Create the User model","text":"

As for any Beanie ODM model, we'll create a User model.

import motor.motor_asyncio\nfrom beanie import Document\nfrom fastapi_users.db import BeanieBaseUser, BeanieUserDatabase\n\nDATABASE_URL = \"mongodb://localhost:27017\"\nclient = motor.motor_asyncio.AsyncIOMotorClient(\n    DATABASE_URL, uuidRepresentation=\"standard\"\n)\ndb = client[\"database_name\"]\n\n\nclass User(BeanieBaseUser, Document):\n    pass\n\n\nasync def get_user_db():\n    yield BeanieUserDatabase(User)\n

As you can see, FastAPI Users provides a base class that will include base fields for our User table. You can of course add you own fields there to fit to your needs!

Info

The base class is configured to automatically create a unique index on id and email.

Info

If you want to add your own custom settings to your User document model - like changing the collection name - don't forget to let your inner Settings class inherit the pre-defined settings from BeanieBaseUser like this: class Settings(BeanieBaseUser.Settings): # ...! See Beanie's documentation on Settings for details.

"},{"location":"configuration/databases/beanie/#create-the-database-adapter","title":"Create the database adapter","text":"

The database adapter of FastAPI Users makes the link between your database configuration and the users logic. It should be generated by a FastAPI dependency.

import motor.motor_asyncio\nfrom beanie import Document\nfrom fastapi_users.db import BeanieBaseUser, BeanieUserDatabase\n\nDATABASE_URL = \"mongodb://localhost:27017\"\nclient = motor.motor_asyncio.AsyncIOMotorClient(\n    DATABASE_URL, uuidRepresentation=\"standard\"\n)\ndb = client[\"database_name\"]\n\n\nclass User(BeanieBaseUser, Document):\n    pass\n\n\nasync def get_user_db():\n    yield BeanieUserDatabase(User)\n

Notice that we pass a reference to the User model we defined above.

"},{"location":"configuration/databases/beanie/#initialize-beanie","title":"Initialize Beanie","text":"

When initializing your FastAPI app, it's important that you initialize Beanie so it can discover your models. We can achieve this using Lifespan Events on the FastAPI app:

from contextlib import asynccontextmanager\nfrom beanie import init_beanie\n\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    await init_beanie(\n        database=db,  # (1)!\n        document_models=[\n            User,  # (2)!\n        ],\n    )\n    yield\n\napp = FastAPI(lifespan=lifespan)\n
  1. This is the db Motor database instance we defined above.

  2. This is the Beanie User model we defined above. Don't forget to also add your very own models!

"},{"location":"configuration/databases/sqlalchemy/","title":"SQLAlchemy","text":"

FastAPI Users provides the necessary tools to work with SQL databases thanks to SQLAlchemy ORM with asyncio.

"},{"location":"configuration/databases/sqlalchemy/#asynchronous-driver","title":"Asynchronous driver","text":"

To work with your DBMS, you'll need to install the corresponding asyncio driver. The common choices are:

  • For PostgreSQL: pip install asyncpg
  • For SQLite: pip install aiosqlite

Examples of DB_URLs are:

  • PostgreSQL: engine = create_engine('postgresql+asyncpg://user:password@host:port/name')
  • SQLite: engine = create_engine('sqlite+aiosqlite:///name.db')

For the sake of this tutorial from now on, we'll use a simple SQLite database.

Warning

When using asynchronous sessions, ensure Session.expire_on_commit is set to False as recommended by the SQLAlchemy docs on asyncio. The examples on this documentation already have this setting correctly defined to False when using the async_sessionmaker factory.

"},{"location":"configuration/databases/sqlalchemy/#create-the-user-model","title":"Create the User model","text":"

As for any SQLAlchemy ORM model, we'll create a User model.

from collections.abc import AsyncGenerator\n\nfrom fastapi import Depends\nfrom fastapi_users.db import SQLAlchemyBaseUserTableUUID, SQLAlchemyUserDatabase\nfrom sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine\nfrom sqlalchemy.orm import DeclarativeBase\n\nDATABASE_URL = \"sqlite+aiosqlite:///./test.db\"\n\n\nclass Base(DeclarativeBase):\n    pass\n\n\nclass User(SQLAlchemyBaseUserTableUUID, Base):\n    pass\n\n\nengine = create_async_engine(DATABASE_URL)\nasync_session_maker = async_sessionmaker(engine, expire_on_commit=False)\n\n\nasync def create_db_and_tables():\n    async with engine.begin() as conn:\n        await conn.run_sync(Base.metadata.create_all)\n\n\nasync def get_async_session() -> AsyncGenerator[AsyncSession, None]:\n    async with async_session_maker() as session:\n        yield session\n\n\nasync def get_user_db(session: AsyncSession = Depends(get_async_session)):\n    yield SQLAlchemyUserDatabase(session, User)\n

As you can see, FastAPI Users provides a base class that will include base fields for our User table. You can of course add you own fields there to fit to your needs!

Primary key is defined as UUID

By default, we use UUID as a primary key ID for your user. If you want to use another type, like an auto-incremented integer, you can use SQLAlchemyBaseUserTable as base class and define your own id column.

class User(SQLAlchemyBaseUserTable[int], Base):\n    id: Mapped[int] = mapped_column(Integer, primary_key=True)\n

Notice that SQLAlchemyBaseUserTable expects a generic type to define the actual type of ID you use.

"},{"location":"configuration/databases/sqlalchemy/#implement-a-function-to-create-the-tables","title":"Implement a function to create the tables","text":"

We'll now create an utility function to create all the defined tables.

from collections.abc import AsyncGenerator\n\nfrom fastapi import Depends\nfrom fastapi_users.db import SQLAlchemyBaseUserTableUUID, SQLAlchemyUserDatabase\nfrom sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine\nfrom sqlalchemy.orm import DeclarativeBase\n\nDATABASE_URL = \"sqlite+aiosqlite:///./test.db\"\n\n\nclass Base(DeclarativeBase):\n    pass\n\n\nclass User(SQLAlchemyBaseUserTableUUID, Base):\n    pass\n\n\nengine = create_async_engine(DATABASE_URL)\nasync_session_maker = async_sessionmaker(engine, expire_on_commit=False)\n\n\nasync def create_db_and_tables():\n    async with engine.begin() as conn:\n        await conn.run_sync(Base.metadata.create_all)\n\n\nasync def get_async_session() -> AsyncGenerator[AsyncSession, None]:\n    async with async_session_maker() as session:\n        yield session\n\n\nasync def get_user_db(session: AsyncSession = Depends(get_async_session)):\n    yield SQLAlchemyUserDatabase(session, User)\n

This function can be called, for example, during the initialization of your FastAPI app.

Warning

In production, it's strongly recommended to setup a migration system to update your SQL schemas. See Alembic.

"},{"location":"configuration/databases/sqlalchemy/#create-the-database-adapter-dependency","title":"Create the database adapter dependency","text":"

The database adapter of FastAPI Users makes the link between your database configuration and the users logic. It should be generated by a FastAPI dependency.

from collections.abc import AsyncGenerator\n\nfrom fastapi import Depends\nfrom fastapi_users.db import SQLAlchemyBaseUserTableUUID, SQLAlchemyUserDatabase\nfrom sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine\nfrom sqlalchemy.orm import DeclarativeBase\n\nDATABASE_URL = \"sqlite+aiosqlite:///./test.db\"\n\n\nclass Base(DeclarativeBase):\n    pass\n\n\nclass User(SQLAlchemyBaseUserTableUUID, Base):\n    pass\n\n\nengine = create_async_engine(DATABASE_URL)\nasync_session_maker = async_sessionmaker(engine, expire_on_commit=False)\n\n\nasync def create_db_and_tables():\n    async with engine.begin() as conn:\n        await conn.run_sync(Base.metadata.create_all)\n\n\nasync def get_async_session() -> AsyncGenerator[AsyncSession, None]:\n    async with async_session_maker() as session:\n        yield session\n\n\nasync def get_user_db(session: AsyncSession = Depends(get_async_session)):\n    yield SQLAlchemyUserDatabase(session, User)\n

Notice that we define first a get_async_session dependency returning us a fresh SQLAlchemy session to interact with the database.

It's then used inside the get_user_db dependency to generate our adapter. Notice that we pass it two things:

  • The session instance we just injected.
  • The User class, which is the actual SQLAlchemy model.
"},{"location":"configuration/routers/","title":"Routers","text":"

We're almost there! The last step is to configure the FastAPIUsers object that will wire the user manager, the authentication classes and let us generate the actual API routes.

"},{"location":"configuration/routers/#configure-fastapiusers","title":"Configure FastAPIUsers","text":"

Configure FastAPIUsers object with the elements we defined before. More precisely:

  • get_user_manager: Dependency callable getter to inject the user manager class instance. See UserManager.
  • auth_backends: List of authentication backends. See Authentication.
import uuid\n\nfrom fastapi_users import FastAPIUsers\n\nfrom .db import User\n\nfastapi_users = FastAPIUsers[User, uuid.UUID](\n    get_user_manager,\n    [auth_backend],\n)\n

Typing: User and ID generic types are expected

You can see that we define two generic types when instantiating:

  • User, which is the user model we defined in the database part
  • The ID, which should correspond to the type of ID you use on your model. Here, we chose UUID, but it can be anything, like an integer or a MongoDB ObjectID.

It'll help you to have good type-checking and auto-completion.

"},{"location":"configuration/routers/#available-routers","title":"Available routers","text":"

This helper class will let you generate useful routers to setup the authentication system. Each of them is optional, so you can pick only the one that you are interested in! Here are the routers provided:

  • Auth router: Provides /login and /logout routes for a given authentication backend.
  • Register router: Provides /register routes to allow a user to create a new account.
  • Reset password router: Provides /forgot-password and /reset-password routes to allow a user to reset its password.
  • Verify router: Provides /request-verify-token and /verify routes to manage user e-mail verification.
  • Users router: Provides routes to manage users.
  • OAuth router: Provides routes to perform an OAuth authentication against a service provider (like Google or Facebook).

You should check out each of them to understand how to use them.

"},{"location":"configuration/routers/auth/","title":"Auth router","text":"

The auth router will generate /login and /logout routes for a given authentication backend.

Check the routes usage to learn how to use them.

"},{"location":"configuration/routers/auth/#setup","title":"Setup","text":"
import uuid\n\nfrom fastapi import FastAPI\nfrom fastapi_users import FastAPIUsers\n\nfrom .db import User\n\nfastapi_users = FastAPIUsers[User, uuid.UUID](\n    get_user_manager,\n    [auth_backend],\n)\n\napp = FastAPI()\napp.include_router(\n    fastapi_users.get_auth_router(auth_backend),\n    prefix=\"/auth/jwt\",\n    tags=[\"auth\"],\n)\n
"},{"location":"configuration/routers/auth/#optional-user-verification","title":"Optional: user verification","text":"

You can require the user to be verified (i.e. is_verified property set to True) to allow login. You have to set the requires_verification parameter to True on the router instantiation method:

app.include_router(\n    fastapi_users.get_auth_router(auth_backend, requires_verification=True),\n    prefix=\"/auth/jwt\",\n    tags=[\"auth\"],\n)\n
"},{"location":"configuration/routers/register/","title":"Register routes","text":"

The register router will generate a /register route to allow a user to create a new account.

Check the routes usage to learn how to use them.

"},{"location":"configuration/routers/register/#setup","title":"Setup","text":"
import uuid\n\nfrom fastapi import FastAPI\nfrom fastapi_users import FastAPIUsers\n\nfrom .db import User\nfrom .schemas import UserCreate, UserRead\n\nfastapi_users = FastAPIUsers[User, uuid.UUID](\n    get_user_manager,\n    [auth_backend],\n)\n\napp = FastAPI()\napp.include_router(\n    fastapi_users.get_register_router(UserRead, UserCreate),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\n
"},{"location":"configuration/routers/reset/","title":"Reset password router","text":"

The reset password router will generate /forgot-password (the user asks for a token to reset its password) and /reset-password (the user changes its password given the token) routes.

Check the routes usage to learn how to use them.

"},{"location":"configuration/routers/reset/#setup","title":"Setup","text":"
import uuid\n\nfrom fastapi import FastAPI\nfrom fastapi_users import FastAPIUsers\n\nfrom .db import User\n\nfastapi_users = FastAPIUsers[User, uuid.UUID](\n    get_user_manager,\n    [auth_backend],\n)\n\napp = FastAPI()\napp.include_router(\n    fastapi_users.get_reset_password_router(),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\n
"},{"location":"configuration/routers/users/","title":"Users router","text":"

This router provides routes to manage users. Check the routes usage to learn how to use them.

"},{"location":"configuration/routers/users/#setup","title":"Setup","text":"
import uuid\n\nfrom fastapi import FastAPI\nfrom fastapi_users import FastAPIUsers\n\nfrom .db import User\nfrom .schemas import UserRead, UserUpdate\n\nfastapi_users = FastAPIUsers[User, uuid.UUID](\n    get_user_manager,\n    [auth_backend],\n)\n\napp = FastAPI()\napp.include_router(\n    fastapi_users.get_users_router(UserRead, UserUpdate),\n    prefix=\"/users\",\n    tags=[\"users\"],\n)\n
"},{"location":"configuration/routers/users/#optional-user-verification","title":"Optional: user verification","text":"

You can require the user to be verified (i.e. is_verified property set to True) to access those routes. You have to set the requires_verification parameter to True on the router instantiation method:

app.include_router(\n    fastapi_users.get_users_router(UserRead, UserUpdate, requires_verification=True),\n    prefix=\"/users\",\n    tags=[\"users\"],\n)\n
"},{"location":"configuration/routers/verify/","title":"Verify router","text":"

This router provides routes to manage user email verification. Check the routes usage to learn how to use them.

\ud83d\udc4f\ud83d\udc4f\ud83d\udc4f

A big thank you to Edd Salkield and Mark Todd who worked hard on this feature!

"},{"location":"configuration/routers/verify/#setup","title":"Setup","text":"
import uuid\n\nfrom fastapi import FastAPI\nfrom fastapi_users import FastAPIUsers\n\nfrom .db import User\nfrom .schemas import UserRead\n\nfastapi_users = FastAPIUsers[User, uuid.UUID](\n    get_user_manager,\n    [auth_backend],\n)\n\napp = FastAPI()\napp.include_router(\n    fastapi_users.get_verify_router(UserRead),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\n
"},{"location":"cookbook/create-user-programmatically/","title":"Create a user programmatically","text":"

Sometimes, you'll need to create a user programmatically in the code rather than passing by the REST API endpoint. To do this, we'll create a function that you can call from your code.

In this context, we are outside the dependency injection mechanism of FastAPI, so we have to take care of instantiating the UserManager class and all other dependent objects manually.

For this cookbook, we'll consider you are starting from the SQLAlchemy full example, but it'll be rather similar for other DBMS.

"},{"location":"cookbook/create-user-programmatically/#1-define-dependencies-as-context-managers","title":"1. Define dependencies as context managers","text":"

Generally, FastAPI dependencies are defined as generators, using the yield keyword. FastAPI knows very well to handle them inside its dependency injection system. For example, here is the definition of the get_user_manager dependency:

async def get_user_manager(user_db: SQLAlchemyUserDatabase = Depends(get_user_db)):\n  yield UserManager(user_db)\n

In Python, when we want to use a generator, we have to use a for loop, which would be a bit unnatural in this context since we have only one value to get, the user manager instance. To avoid this, we'll transform them into context managers, so we can call them using the with..as syntax. Fortunately, the standard library provides tools to automatically transform generators into context managers.

In the following sample, we import our dependencies and create a context manager version using contextlib.asynccontextmanager:

import contextlib\n\nfrom app.db import get_async_session, get_user_db\nfrom app.schemas import UserCreate\nfrom app.users import get_user_manager\nfrom fastapi_users.exceptions import UserAlreadyExists\n\nget_async_session_context = contextlib.asynccontextmanager(get_async_session)\nget_user_db_context = contextlib.asynccontextmanager(get_user_db)\nget_user_manager_context = contextlib.asynccontextmanager(get_user_manager)\n\n\nasync def create_user(email: str, password: str, is_superuser: bool = False):\n    try:\n        async with get_async_session_context() as session:\n            async with get_user_db_context(session) as user_db:\n                async with get_user_manager_context(user_db) as user_manager:\n                    user = await user_manager.create(\n                        UserCreate(\n                            email=email, password=password, is_superuser=is_superuser\n                        )\n                    )\n                    print(f\"User created {user}\")\n                    return user\n    except UserAlreadyExists:\n        print(f\"User {email} already exists\")\n        raise\n

I have other dependencies

Since FastAPI Users fully embraces dependency injection, you may have more arguments passed to your database or user manager dependencies. It's important then to not forget anyone. Once again, outside the dependency injection system, you are responsible of instantiating everything yourself.

"},{"location":"cookbook/create-user-programmatically/#2-write-a-function","title":"2. Write a function","text":"

We are now ready to write a function. The example below shows you a basic example but you can of course adapt it to your own needs. The key part here is once again to take care of opening every context managers and pass them every required arguments, as the dependency manager would do.

import contextlib\n\nfrom app.db import get_async_session, get_user_db\nfrom app.schemas import UserCreate\nfrom app.users import get_user_manager\nfrom fastapi_users.exceptions import UserAlreadyExists\n\nget_async_session_context = contextlib.asynccontextmanager(get_async_session)\nget_user_db_context = contextlib.asynccontextmanager(get_user_db)\nget_user_manager_context = contextlib.asynccontextmanager(get_user_manager)\n\n\nasync def create_user(email: str, password: str, is_superuser: bool = False):\n    try:\n        async with get_async_session_context() as session:\n            async with get_user_db_context(session) as user_db:\n                async with get_user_manager_context(user_db) as user_manager:\n                    user = await user_manager.create(\n                        UserCreate(\n                            email=email, password=password, is_superuser=is_superuser\n                        )\n                    )\n                    print(f\"User created {user}\")\n                    return user\n    except UserAlreadyExists:\n        print(f\"User {email} already exists\")\n        raise\n
"},{"location":"cookbook/create-user-programmatically/#3-use-it","title":"3. Use it","text":"

You can now easily use it in a script. For example:

import asyncio\n\nif __name__ == \"__main__\":\n  asyncio.run(create_user(\"king.arthur@camelot.bt\", \"guinevere\"))\n
"},{"location":"migration/08_to_1x/","title":"0.8.x \u27a1\ufe0f 1.x.x","text":"

1.0 version introduces major breaking changes that need you to update some of your code and migrate your data.

"},{"location":"migration/08_to_1x/#id-are-uuid","title":"Id. are UUID","text":"

Users and OAuth accounts id. are now represented as real UUID objects instead of plain strings. This change was introduced to leverage efficient storage and indexing for DBMS that supports UUID (especially PostgreSQL and Mongo).

"},{"location":"migration/08_to_1x/#in-python-code","title":"In Python code","text":"

If you were doing comparison betwen a user id. and a string (in unit tests for example), you should now cast the id. to string:

# Before\nassert \"d35d213e-f3d8-4f08-954a-7e0d1bea286f\" == user.id\n\n# Now\nassert \"d35d213e-f3d8-4f08-954a-7e0d1bea286f\" == str(user.id)\n

If you were refering to user id. in your Pydantic models, the field should now be of UUID4 type instead of str:

from pydantic import BaseModel, UUID4\n\n# Before\nclass Model(BaseModel):\n    user_id: str\n\n# After\nclass Model(BaseModel):\n    user_id: UUID4\n
"},{"location":"migration/08_to_1x/#mongodb","title":"MongoDB","text":"

To avoid any issues, it's recommended to use the standard UUID representation when instantiating the MongoDB client:

DATABASE_URL = \"mongodb://localhost:27017\"\nclient = motor.motor_asyncio.AsyncIOMotorClient(\n    DATABASE_URL, uuidRepresentation=\"standard\"\n)\n

This parameter controls how the UUID values will be encoded in the database. By default, it's set to pythonLegacy but new applications should consider setting this to standard for cross language compatibility. Read more about this.

"},{"location":"migration/08_to_1x/#in-database","title":"In database","text":"

Id. were before stored as strings in the database. You should make a migration to convert string data to UUID data.

Danger

Scripts below are provided as guidelines. Please review them carefully, adapt them and check that they are working on a test database before applying them to production. BE CAREFUL. THEY CAN DESTROY YOUR DATA..

"},{"location":"migration/08_to_1x/#postgresql","title":"PostgreSQL","text":"

PostgreSQL supports UUID type. If not already, you should enable the uuid-ossp extension:

CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";\n

To convert the existing id. string column, we can:

  1. Create a new column with UUID type.
  2. Fill it with the id. converted to UUID.
  3. Drop the original id. column.
  4. Make the new column a primary key and rename it.
ALTER TABLE \"user\" ADD uuid_id UUID;\nUPDATE \"user\" SET uuid_id = uuid(id);\nALTER TABLE \"user\" DROP id;\nALTER TABLE \"user\" ADD PRIMARY KEY (uuid_id);\nALTER TABLE \"user\" RENAME COLUMN uuid_id TO id;\n
"},{"location":"migration/08_to_1x/#mysql","title":"MySQL","text":"

MySQL doesn't support UUID type. We'll just convert the column to CHAR(36) type:

ALTER TABLE \"user\" MODIFY id CHAR(36);\n
"},{"location":"migration/08_to_1x/#mongodb_1","title":"MongoDB","text":""},{"location":"migration/08_to_1x/#mongo-shell","title":"Mongo shell","text":"

For MongoDB, we can use a forEach iterator to convert the id. for each document:

db.getCollection('users').find().forEach(function(user) {\n  var uuid = UUID(user.id);\n  db.getCollection('users').update({_id: user._id}, [{$set: {id: uuid}}]);\n});\n
"},{"location":"migration/08_to_1x/#python","title":"Python","text":"
import uuid\n\nimport motor.motor_asyncio\n\n\nasync def migrate_uuid():\n    client = motor.motor_asyncio.AsyncIOMotorClient(\n        DATABASE_URL, uuidRepresentation=\"standard\"\n    )\n    db = client[\"database_name\"]\n    users = db[\"users\"]\n\n    async for user in users.find({}):\n        await users.update_one(\n            {\"_id\": user[\"_id\"]},\n            {\"$set\": {\"id\": uuid.UUID(user[\"id\"])}},\n        )\n
"},{"location":"migration/08_to_1x/#splitted-routers","title":"Splitted routers","text":"

You now have the responsibility to wire the routers. FastAPI Users doesn't give a bloated users router anymore.

Event handlers are also removed. You have to provide your \"after-\" logic as a parameter of the router generator.

"},{"location":"migration/08_to_1x/#before","title":"Before","text":"
jwt_authentication = JWTAuthentication(secret=SECRET, lifetime_seconds=3600)\n\napp = FastAPI()\nfastapi_users = FastAPIUsers(\n    user_db, [jwt_authentication], User, UserCreate, UserUpdate, UserDB,\n)\napp.include_router(fastapi_users.router, prefix=\"/users\", tags=[\"users\"])\n\n\n@fastapi_users.on_after_register()\ndef on_after_register(user: User, request: Request):\n    print(f\"User {user.id} has registered.\")\n\n\n@fastapi_users.on_after_forgot_password()\ndef on_after_forgot_password(user: User, token: str, request: Request):\n    print(f\"User {user.id} has forgot their password. Reset token: {token}\")\n
"},{"location":"migration/08_to_1x/#after","title":"After","text":"
def on_after_register(user: UserDB, request: Request):\n    print(f\"User {user.id} has registered.\")\n\n\ndef on_after_forgot_password(user: UserDB, token: str, request: Request):\n    print(f\"User {user.id} has forgot their password. Reset token: {token}\")\n\n\njwt_authentication = JWTAuthentication(secret=SECRET, lifetime_seconds=3600)\n\napp = FastAPI()\nfastapi_users = FastAPIUsers(\n    user_db, [jwt_authentication], User, UserCreate, UserUpdate, UserDB,\n)\napp.include_router(\n    fastapi_users.get_auth_router(jwt_authentication), prefix=\"/auth/jwt\", tags=[\"auth\"]\n)\napp.include_router(\n    fastapi_users.get_register_router(on_after_register), prefix=\"/auth\", tags=[\"auth\"]\n)\napp.include_router(\n    fastapi_users.get_reset_password_router(\n        SECRET, after_forgot_password=on_after_forgot_password\n    ),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\napp.include_router(fastapi_users.get_users_router(), prefix=\"/users\", tags=[\"users\"])\n

Important things to notice:

  • FastAPIUsers takes two arguments less (reset_password_token_secret and reset_password_token_lifetime_seconds).
  • You have more flexibility to choose the prefix and tags of the routers.
  • The /login//logout are now your responsibility to include for each backend. The path will change (before /login/jwt, after /jwt/login).
  • If you don't care about some of those routers, you can discard them.
"},{"location":"migration/1x_to_2x/","title":"1.x.x \u27a1\ufe0f 2.x.x","text":""},{"location":"migration/1x_to_2x/#jwt-authentication-backend","title":"JWT authentication backend","text":"

To be fully compatible with Swagger authentication, the output of a successful login operation with the JWT authentication backend has changed:

Before

{\n    \"token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiOTIyMWZmYzktNjQwZi00MzcyLTg2ZDMtY2U2NDJjYmE1NjAzIiwiYXVkIjoiZmFzdGFwaS11c2VyczphdXRoIiwiZXhwIjoxNTcxNTA0MTkzfQ.M10bjOe45I5Ncu_uXvOmVV8QxnL-nZfcH96U90JaocI\"\n}\n

After

{\n    \"access_token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiOTIyMWZmYzktNjQwZi00MzcyLTg2ZDMtY2U2NDJjYmE1NjAzIiwiYXVkIjoiZmFzdGFwaS11c2VyczphdXRoIiwiZXhwIjoxNTcxNTA0MTkzfQ.M10bjOe45I5Ncu_uXvOmVV8QxnL-nZfcH96U90JaocI\",\n    \"token_type\": \"bearer\"\n}\n

Make sure to update your clients to read the token in the right property.

"},{"location":"migration/2x_to_3x/","title":"2.x.x \u27a1\ufe0f 3.x.x","text":""},{"location":"migration/2x_to_3x/#emails-are-now-case-insensitive","title":"Emails are now case-insensitive","text":"

Before 3.x.x, the local part (before the @) of the email address was case-sensitive. Therefore, king.arthur@camelot.bt and King.Arthur@camelot.bt were considered as two different users. This behaviour was a bit confusing and not consistent with 99% of web services out there.

After 3.x.x, users are fetched from the database with a case-insensitive email search. Bear in mind though that if the user registers with the email King.Arthur@camelot.bt, it will be stored exactly like this in the database (with casing) ; but he will be able to login as king.arthur@camelot.bt.

Danger

It's super important then, before you upgrade to 3.x.x that you check if there are several users with the same email with different cases ; and that you merge or delete those accounts.

"},{"location":"migration/3x_to_4x/","title":"3.x.x \u27a1\ufe0f 4.x.x","text":""},{"location":"migration/3x_to_4x/#expires_at-property-in-oauthaccount-is-now-optional","title":"expires_at property in OAuthAccount is now optional","text":"

Before 4.x.x, the expires_at property in OAuthAccount model was mandatory. It was causing issues with some services that don't have such expiration property.

If you use SQLAlchemy or Tortoise databases adapters, you'll have to make a migration to update your database schema.

"},{"location":"migration/4x_to_5x/","title":"4.x.x \u27a1\ufe0f 5.x.x","text":""},{"location":"migration/4x_to_5x/#new-property-is_verified-in-user-model","title":"New property is_verified in User model.","text":"

Starting 5.x.x., there is a new e-mail verification feature. Even if optional, the is_verified property has been added to the User model.

If you use SQLAlchemy or Tortoise databases adapters, you'll have to make a migration to update your database schema.

"},{"location":"migration/6x_to_7x/","title":"6.x.x \u27a1\ufe0f 7.x.x","text":"
  • The deprecated dependencies to retrieve current user have been removed. Use the current_user factory instead. [Documentation]
  • When trying to authenticate a not verified user, a status code 403 is raised instead of status code 401. Thanks @daanbeverdam \ud83c\udf89 [Documentation]
  • Your UserUpdate model shouldn't inherit from the base User class. If you have custom fields, you should repeat them in this model. [Documentation]
  • Database adapters now live in their own repositories and packages.
  • When upgrading to v7.0.0, the dependency for your database adapter should automatically be installed.
  • The import statements remain unchanged.
"},{"location":"migration/7x_to_8x/","title":"7.x.x \u27a1\ufe0f 8.x.x","text":"

Version 8 includes the biggest code changes since version 1. We reorganized lot of parts of the code to make it even more modular and integrate more into the dependency injection system of FastAPI.

Most importantly, you need now to implement a UserManager class and a associated dependency to create an instance of this class.

"},{"location":"migration/7x_to_8x/#event-handlers-should-live-in-the-usermanager","title":"Event handlers should live in the UserManager","text":"

Before, event handlers like on_after_register or on_after_forgot_password were defined in their own functions that were passed as arguments of router generators.

Now, they should be methods of the UserManager class.

You can read more in the UserManager documentation.

"},{"location":"migration/7x_to_8x/#password-validation-should-live-in-the-usermanager","title":"Password validation should live in the UserManager","text":"

Before, password validation was defined in its own function that was passed as argument of FastAPIUsers.

Now, it should be a method of the UserManager class.

You can read more in the UserManager documentation.

"},{"location":"migration/7x_to_8x/#verify-token-secret-and-lifetime-parameters-are-attributes-of-usermanager","title":"Verify token secret and lifetime parameters are attributes of UserManager","text":"

Before, verify token and lifetime parameters were passed as argument of get_verify_router.

Now, they should be defined as attributes of the UserManager class.

You can read more in the UserManager documentation.

"},{"location":"migration/7x_to_8x/#reset-password-token-secret-and-lifetime-parameters-are-attributes-of-usermanager","title":"Reset password token secret and lifetime parameters are attributes of UserManager","text":"

Before, reset password token and lifetime parameters were passed as argument of get_verify_router.

Now, they should be defined as attributes of the UserManager class.

You can read more in the UserManager documentation.

"},{"location":"migration/7x_to_8x/#database-adapter-should-be-provided-in-a-dependency","title":"Database adapter should be provided in a dependency","text":"

Before, we advised to directly instantiate the database adapter class.

Now, it should be instantiated inside a dependency that you define yourself. The benefit of this is that it lives in the dependency injection system of FastAPI, allowing you to have more dynamic logic to create your instance.

\u27a1\ufe0f I'm using SQLAlchemy

\u27a1\ufe0f I'm using MongoDB

\u27a1\ufe0f I'm using Tortoise ORM

\u27a1\ufe0f I'm using ormar

"},{"location":"migration/7x_to_8x/#fastapiusers-now-expect-a-get_user_manager-dependency","title":"FastAPIUsers now expect a get_user_manager dependency","text":"

Before, the database adapter instance was passed as argument of FastAPIUsers.

Now, you should define a get_user_manager dependency returning an instance of your UserManager class. This dependency will be dependent of the database adapter dependency.

You can read more in the UserManager documentation and FastAPIUsers documentation

"},{"location":"migration/7x_to_8x/#lost","title":"Lost?","text":"

If you're unsure or a bit lost, make sure to check the full working examples.

"},{"location":"migration/8x_to_9x/","title":"8.x.x \u27a1\ufe0f 9.x.x","text":"

Version 9 revamps the authentication backends: we splitted the logic of a backend into two: the transport, which is how the token will be carried over the request and the strategy, which is how the token is generated and secured.

The benefit of this is that we'll soon be able to propose new strategies, like database session tokens, without having to repeat the transport logic which remains the same.

"},{"location":"migration/8x_to_9x/#convert-the-authentication-backend","title":"Convert the authentication backend","text":"

You now have to generate an authentication backend with a transport and a strategy.

"},{"location":"migration/8x_to_9x/#i-used-jwtauthentication","title":"I used JWTAuthentication","text":"BeforeAfter
from fastapi_users.authentication import JWTAuthentication\n\njwt_authentication = JWTAuthentication(\n    secret=SECRET, lifetime_seconds=3600, tokenUrl=\"auth/jwt/login\"\n)\n
from fastapi_users.authentication import AuthenticationBackend, BearerTransport, JWTStrategy\n\nSECRET = \"SECRET\"\n\nbearer_transport = BearerTransport(tokenUrl=\"auth/jwt/login\")\n\ndef get_jwt_strategy() -> JWTStrategy:\n    return JWTStrategy(secret=SECRET, lifetime_seconds=3600)\n\nauth_backend = AuthenticationBackend(\n    name=\"jwt\",\n    transport=bearer_transport,\n    get_strategy=get_jwt_strategy,\n)\n

Warning

There is no default name anymore: you need to provide it yourself for each of your backends.

"},{"location":"migration/8x_to_9x/#i-used-cookieauthentication","title":"I used CookieAuthentication","text":"BeforeAfter
from fastapi_users.authentication import CookieAuthentication\n\ncookie_authentication = CookieAuthentication(secret=SECRET, lifetime_seconds=3600)\n
from fastapi_users.authentication import AuthenticationBackend, CookieTransport, JWTStrategy\n\nSECRET = \"SECRET\"\n\ncookie_transport = CookieTransport(cookie_max_age=3600)\n\ndef get_jwt_strategy() -> JWTStrategy:\n    return JWTStrategy(secret=SECRET, lifetime_seconds=3600)\n\nauth_backend = AuthenticationBackend(\n    name=\"cookie\",\n    transport=cookie_transport,\n    get_strategy=get_jwt_strategy,\n)\n

Warning

There is no default name anymore: you need to provide it yourself for each of your backends.

Tip

Notice that the strategy is the same for both authentication backends. That's the beauty of this approach: the token generation is decoupled from its transport.

"},{"location":"migration/8x_to_9x/#oauth-one-router-for-each-backend","title":"OAuth: one router for each backend","text":"

Before, a single OAuth router was enough to login with any of your authentication backend. Now, you need to generate a router for each of your backends.

BeforeAfter
app.include_router(\n    fastapi_users.get_oauth_router(google_oauth_client, \"SECRET\"),\n    prefix=\"/auth/google\",\n    tags=[\"auth\"],\n)\n
app.include_router(\n    fastapi_users.get_oauth_router(google_oauth_client, auth_backend, \"SECRET\"),\n    prefix=\"/auth/google\",\n    tags=[\"auth\"],\n)\n
"},{"location":"migration/8x_to_9x/#authentication_backend-is-not-needed-on-authorize","title":"authentication_backend is not needed on /authorize","text":"

The consequence of this is that you don't need to specify the authentication backend when making a request to /authorize.

BeforeAfter
curl \\\n-H \"Content-Type: application/json\" \\\n-X GET \\\nhttp://localhost:8000/auth/google/authorize?authentication_backend=jwt\n
curl \\\n-H \"Content-Type: application/json\" \\\n-X GET \\\nhttp://localhost:8000/auth/google/authorize\n
"},{"location":"migration/8x_to_9x/#lost","title":"Lost?","text":"

If you're unsure or a bit lost, make sure to check the full working examples.

"},{"location":"migration/9x_to_10x/","title":"9.x.x \u27a1\ufe0f 10.x.x","text":"

Version 10 marks important changes in how we manage User models and their ID.

Before, we were relying only on Pydantic models to work with users. In particular the current_user dependency would return you an instance of UserDB, a Pydantic model. This proved to be quite problematic with some ORM if you ever needed to retrieve relationship data or make specific requests.

Now, FastAPI Users is designed to always return you a native object for your ORM model, whether it's an SQLAlchemy model or a Beanie document. Pydantic models are now only used for validation and serialization inside the API.

Before, we were forcing the use of UUID as primary key ID; a consequence of the design above. This proved to be quite problematic on some databases, like MongoDB which uses a special ObjectID format by default. Some SQL folks also prefer to use traditional auto-increment integers.

Now, FastAPI Users is designed to use generic ID type. It means that you can use any type you want for your user's ID. By default, SQLAlchemy adapter still use UUID; but you can quite easily switch to another thing, like an integer. Beanie adapter for MongoDB will use native ObjectID by default, but it also can be overriden.

As you may have guessed, those changes imply quite a lot of breaking changes.

"},{"location":"migration/9x_to_10x/#user-models-and-database-adapter","title":"User models and database adapter","text":""},{"location":"migration/9x_to_10x/#sqlalchemy-orm","title":"SQLAlchemy ORM","text":"

We've removed the old SQLAlchemy dependency support, so the dependency is now fastapi-users[sqlalchemy].

BeforeAfter
fastapi\nfastapi-users[sqlalchemy2]\nuvicorn[standard]\naiosqlite\n
fastapi\nfastapi-users[sqlalchemy]\nuvicorn[standard]\naiosqlite\n

The User model base class for SQLAlchemy slightly changed to support UUID by default.

We changed the name of the class from UserTable to User: it's not a compulsory change, but since there is no risk of confusion with Pydantic models anymore, it's probably a more idiomatic naming.

BeforeAfter
class UserTable(Base, SQLAlchemyBaseUserTable):\n    pass\n
class User(SQLAlchemyBaseUserTableUUID, Base):\n    pass\n

Instantiating the SQLAlchemyUserDatabase adapter now only expects this User model. UserDB is removed.

BeforeAfter
async def get_user_db(session: AsyncSession = Depends(get_async_session)):\n    yield SQLAlchemyUserDatabase(UserDB, session, UserTable)\n
async def get_user_db(session: AsyncSession = Depends(get_async_session)):\n    yield SQLAlchemyUserDatabase(session, User)\n
"},{"location":"migration/9x_to_10x/#mongodb","title":"MongoDB","text":"

MongoDB support is now only provided through Beanie ODM. Even if you don't use it for the rest of your project, it's a very light addition that shouldn't interfere much.

BeforeAfter
fastapi\nfastapi-users[mongodb]\nuvicorn[standard]\naiosqlite\n
fastapi\nfastapi-users[beanie]\nuvicorn[standard]\naiosqlite\n

You now need to define a proper User model using Beanie.

BeforeAfter
import os\n\nimport motor.motor_asyncio\nfrom fastapi_users.db import MongoDBUserDatabase\n\nfrom app.models import UserDB\n\nDATABASE_URL = os.environ[\"DATABASE_URL\"]\nclient = motor.motor_asyncio.AsyncIOMotorClient(\n    DATABASE_URL, uuidRepresentation=\"standard\"\n)\ndb = client[\"database_name\"]\ncollection = db[\"users\"]\n\n\nasync def get_user_db():\n    yield MongoDBUserDatabase(UserDB, collection)\n
import motor.motor_asyncio\nfrom beanie import PydanticObjectId\nfrom fastapi_users.db import BeanieBaseUser, BeanieUserDatabase\n\nDATABASE_URL = \"mongodb://localhost:27017\"\nclient = motor.motor_asyncio.AsyncIOMotorClient(\n    DATABASE_URL, uuidRepresentation=\"standard\"\n)\ndb = client[\"database_name\"]\n\n\nclass User(BeanieBaseUser[PydanticObjectId]):\n    pass\n\n\nasync def get_user_db():\n    yield BeanieUserDatabase(User)\n

ID are now ObjectID by default

By default, User ID will now be native MongoDB ObjectID. If you don't want to make the transition and keep UUID you can do so by overriding the id field:

import uuid\n\nfrom pydantic import Field\n\n\nclass User(BeanieBaseUser[uuid.UUID]):\n    id: uuid.UUID = Field(default_factory=uuid.uuid4)\n

Beanie also needs to be initialized in a startup event handler of your FastAPI app:

from beanie import init_beanie\n\n\n@app.on_event(\"startup\")\nasync def on_startup():\n    await init_beanie(\n        database=db,\n        document_models=[\n            User,\n        ],\n    )\n
"},{"location":"migration/9x_to_10x/#tortoise-orm-and-ormar","title":"Tortoise ORM and ormar","text":"

Unfortunately, we sometimes need to make difficult choices to keep things sustainable. That's why we decided to not support Tortoise ORM and ormar anymore. It appeared they were not widely used.

You can still add support for those ORM yourself by implementing the necessary adapter. You can take inspiration from the SQLAlchemy one.

"},{"location":"migration/9x_to_10x/#usermanager","title":"UserManager","text":"

There is some slight changes on the UserManager class. In particular, it now needs a parse_id method that can be provided through built-in mixins.

Generic typing now expects your native User model class and the type of ID.

The user_db_model class property is removed.

BeforeAfter
class UserManager(BaseUserManager[UserCreate, UserDB]):\n    user_db_model = UserDB\n    reset_password_token_secret = SECRET\n    verification_token_secret = SECRET\n\n    async def on_after_register(self, user: UserDB, request: Optional[Request] = None):\n        print(f\"User {user.id} has registered.\")\n\n    async def on_after_forgot_password(\n        self, user: UserDB, token: str, request: Optional[Request] = None\n    ):\n        print(f\"User {user.id} has forgot their password. Reset token: {token}\")\n\n    async def on_after_request_verify(\n        self, user: UserDB, token: str, request: Optional[Request] = None\n    ):\n        print(f\"Verification requested for user {user.id}. Verification token: {token}\")\n
class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):\n    reset_password_token_secret = SECRET\n    verification_token_secret = SECRET\n\n    async def on_after_register(self, user: User, request: Optional[Request] = None):\n        print(f\"User {user.id} has registered.\")\n\n    async def on_after_forgot_password(\n        self, user: User, token: str, request: Optional[Request] = None\n    ):\n        print(f\"User {user.id} has forgot their password. Reset token: {token}\")\n\n    async def on_after_request_verify(\n        self, user: User, token: str, request: Optional[Request] = None\n    ):\n        print(f\"Verification requested for user {user.id}. Verification token: {token}\")\n

If you need to support other types of ID, you can read more about it in the dedicated section.

"},{"location":"migration/9x_to_10x/#pydantic-models","title":"Pydantic models","text":"

To better distinguish them from the ORM models, Pydantic models are now called schemas.

UserDB has been removed in favor of native models.

We changed the name of User to UserRead: it's not a compulsory change, but since there is a risk of confusion with the native model, it's highly recommended.

Besides, the BaseUser schema now accepts a generic type to specify the type of ID you use.

BeforeAfter
from fastapi_users import models\n\n\nclass User(models.BaseUser):\n    pass\n\n\nclass UserCreate(models.BaseUserCreate):\n    pass\n\n\nclass UserUpdate(models.BaseUserUpdate):\n    pass\n\n\nclass UserDB(User, models.BaseUserDB):\n    pass\n
import uuid\n\nfrom fastapi_users import schemas\n\n\nclass UserRead(schemas.BaseUser[uuid.UUID]):\n    pass\n\n\nclass UserCreate(schemas.BaseUserCreate):\n    pass\n\n\nclass UserUpdate(schemas.BaseUserUpdate):\n    pass\n
"},{"location":"migration/9x_to_10x/#fastapi-users-and-routers","title":"FastAPI Users and routers","text":"

Pydantic schemas are now way less important in this new design. As such, you don't need to pass them when initializing the FastAPIUsers class:

BeforeAfter
fastapi_users = FastAPIUsers(\n    get_user_manager,\n    [auth_backend],\n    User,\n    UserCreate,\n    UserUpdate,\n    UserDB,\n)\n
fastapi_users = FastAPIUsers[User, uuid.UUID](\n    get_user_manager,\n    [auth_backend],\n)\n

As a consequence, those schemas need to be passed when initializing the router that needs them: get_register_router, get_verify_router and get_users_router.

BeforeAfter
app.include_router(\n    fastapi_users.get_auth_router(auth_backend), prefix=\"/auth/jwt\", tags=[\"auth\"]\n)\napp.include_router(fastapi_users.get_register_router(), prefix=\"/auth\", tags=[\"auth\"])\napp.include_router(\n    fastapi_users.get_reset_password_router(),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\napp.include_router(\n    fastapi_users.get_verify_router(),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\napp.include_router(fastapi_users.get_users_router(), prefix=\"/users\", tags=[\"users\"])\n
app.include_router(\n    fastapi_users.get_auth_router(auth_backend), prefix=\"/auth/jwt\", tags=[\"auth\"]\n)\napp.include_router(\n    fastapi_users.get_register_router(UserRead, UserCreate),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\napp.include_router(\n    fastapi_users.get_reset_password_router(),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\napp.include_router(\n    fastapi_users.get_verify_router(UserRead),\n    prefix=\"/auth\",\n    tags=[\"auth\"],\n)\napp.include_router(\n    fastapi_users.get_users_router(UserRead, UserUpdate),\n    prefix=\"/users\",\n    tags=[\"users\"],\n)\n
"},{"location":"migration/9x_to_10x/#lost","title":"Lost?","text":"

If you're unsure or a bit lost, make sure to check the full working examples.

"},{"location":"usage/current-user/","title":"Get current user","text":"

FastAPI Users provides a dependency callable to easily inject authenticated user in your routes. They are available from your FastAPIUsers instance.

Tip

For more information about how to make an authenticated request to your API, check the documentation of your Authentication method.

"},{"location":"usage/current-user/#current_user","title":"current_user","text":"

Return a dependency callable to retrieve currently authenticated user, passing the following parameters:

  • optional: If True, None is returned if there is no authenticated user or if it doesn't pass the other requirements. Otherwise, throw 401 Unauthorized. Defaults to False.
  • active: If True, throw 401 Unauthorized if the authenticated user is inactive. Defaults to False.
  • verified: If True, throw 403 Forbidden if the authenticated user is not verified. Defaults to False.
  • superuser: If True, throw 403 Forbidden if the authenticated user is not a superuser. Defaults to False.
  • get_enabled_backends: Optional dependency callable returning a list of enabled authentication backends. Useful if you want to dynamically enable some authentication backends based on external logic, like a configuration in database. By default, all specified authentication backends are enabled. Please not however that every backends will appear in the OpenAPI documentation, as FastAPI resolves it statically.

Create it once and reuse it

This function is a factory, a function returning another function \ud83e\udd2f

It's this returned function that will be the dependency called by FastAPI in your API routes.

To avoid having to generate it on each route and avoid issues when unit testing, it's strongly recommended that you assign the result in a variable and reuse it at will in your routes. The examples below demonstrate this pattern.

"},{"location":"usage/current-user/#examples","title":"Examples","text":""},{"location":"usage/current-user/#get-the-current-user-active-or-not","title":"Get the current user (active or not)","text":"
current_user = fastapi_users.current_user()\n\n@app.get(\"/protected-route\")\ndef protected_route(user: User = Depends(current_user)):\n    return f\"Hello, {user.email}\"\n
"},{"location":"usage/current-user/#get-the-current-active-user","title":"Get the current active user","text":"
current_active_user = fastapi_users.current_user(active=True)\n\n@app.get(\"/protected-route\")\ndef protected_route(user: User = Depends(current_active_user)):\n    return f\"Hello, {user.email}\"\n
"},{"location":"usage/current-user/#get-the-current-active-and-verified-user","title":"Get the current active and verified user","text":"
current_active_verified_user = fastapi_users.current_user(active=True, verified=True)\n\n@app.get(\"/protected-route\")\ndef protected_route(user: User = Depends(current_active_verified_user)):\n    return f\"Hello, {user.email}\"\n
"},{"location":"usage/current-user/#get-the-current-active-superuser","title":"Get the current active superuser","text":"
current_superuser = fastapi_users.current_user(active=True, superuser=True)\n\n@app.get(\"/protected-route\")\ndef protected_route(user: User = Depends(current_superuser)):\n    return f\"Hello, {user.email}\"\n
"},{"location":"usage/current-user/#dynamically-enable-authentication-backends","title":"Dynamically enable authentication backends","text":"

Warning

This is an advanced feature for cases where you have several authentication backends that are enabled conditionally. In most cases, you won't need this option.

from fastapi import Request\nfrom fastapi_users.authentication import AuthenticationBackend, BearerTransport, CookieTransport, JWTStrategy\n\nSECRET = \"SECRET\"\n\nbearer_transport = BearerTransport(tokenUrl=\"auth/jwt/login\")\ncookie_transport = CookieTransport(cookie_max_age=3600)\n\ndef get_jwt_strategy() -> JWTStrategy:\n    return JWTStrategy(secret=SECRET, lifetime_seconds=3600)\n\njwt_backend = AuthenticationBackend(\n    name=\"jwt\",\n    transport=bearer_transport,\n    get_strategy=get_jwt_strategy,\n)\ncookie_backend = AuthenticationBackend(\n    name=\"jwt\",\n    transport=cookie_transport,\n    get_strategy=get_jwt_strategy,\n)\n\nasync def get_enabled_backends(request: Request):\n    \"\"\"Return the enabled dependencies following custom logic.\"\"\"\n    if request.url.path == \"/protected-route-only-jwt\":\n        return [jwt_backend]\n    else:\n        return [cookie_backend, jwt_backend]\n\n\ncurrent_active_user = fastapi_users.current_user(active=True, get_enabled_backends=get_enabled_backends)\n\n\n@app.get(\"/protected-route\")\ndef protected_route(user: User = Depends(current_active_user)):\n    return f\"Hello, {user.email}. You are authenticated with a cookie or a JWT.\"\n\n\n@app.get(\"/protected-route-only-jwt\")\ndef protected_route(user: User = Depends(current_active_user)):\n    return f\"Hello, {user.email}. You are authenticated with a JWT.\"\n
"},{"location":"usage/current-user/#in-a-path-operation","title":"In a path operation","text":"

If you don't need the user in the route logic, you can use this syntax:

@app.get(\"/protected-route\", dependencies=[Depends(current_superuser)])\ndef protected_route():\n    return \"Hello, some user.\"\n

You can read more about this in FastAPI docs.

"},{"location":"usage/flow/","title":"Flow","text":"

This page will present you a complete registration and authentication flow once you've setup FastAPI Users. Each example will be presented with a cURL and an axios example.

"},{"location":"usage/flow/#1-registration","title":"1. Registration","text":"

First step, of course, is to register as a user.

"},{"location":"usage/flow/#request","title":"Request","text":"cURLaxios
curl \\\n-H \"Content-Type: application/json\" \\\n-X POST \\\n-d \"{\\\"email\\\": \\\"king.arthur@camelot.bt\\\",\\\"password\\\": \\\"guinevere\\\"}\" \\\nhttp://localhost:8000/auth/register\n
axios.post('http://localhost:8000/auth/register', {\n    email: 'king.arthur@camelot.bt',\n    password: 'guinevere',\n})\n.then((response) => console.log(response))\n.catch((error) => console.log(error));\n
"},{"location":"usage/flow/#response","title":"Response","text":"

You'll get a JSON response looking like this:

{\n    \"id\": \"4fd3477b-eccf-4ee3-8f7d-68ad72261476\",\n    \"email\": \"king.arthur@camelot.bt\",\n    \"is_active\": true,\n    \"is_superuser\": false\n}\n

Info

Several things to bear in mind:

  • If you have defined other required fields in your User model (like a first name or a birthdate), you'll have to provide them in the payload.
  • The user is active by default.
  • The user cannot set is_active or is_superuser itself at registration. Only a superuser can do it by PATCHing the user.
"},{"location":"usage/flow/#2-login","title":"2. Login","text":"

Now, you can login as this new user.

You can generate a login route for each authentication backend. Each backend will have a different response.

"},{"location":"usage/flow/#bearer-jwt","title":"Bearer + JWT","text":""},{"location":"usage/flow/#request_1","title":"Request","text":"cURLaxios
curl \\\n-H \"Content-Type: multipart/form-data\" \\\n-X POST \\\n-F \"username=king.arthur@camelot.bt\" \\\n-F \"password=guinevere\" \\\nhttp://localhost:8000/auth/jwt/login\n
const formData = new FormData();\nformData.set('username', 'king.arthur@camelot.bt');\nformData.set('password', 'guinevere');\naxios.post(\n    'http://localhost:8000/auth/jwt/login',\n    formData,\n    {\n        headers: {\n            'Content-Type': 'multipart/form-data',\n        },\n    },\n)\n.then((response) => console.log(response))\n.catch((error) => console.log(error));\n

Warning

Notice that we don't send it as a JSON payload here but with form data instead. Also, the email is provided by a field named username.

"},{"location":"usage/flow/#response_1","title":"Response","text":"

You'll get a JSON response looking like this:

{\n    \"access_token\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiNGZkMzQ3N2ItZWNjZi00ZWUzLThmN2QtNjhhZDcyMjYxNDc2IiwiYXVkIjoiZmFzdGFwaS11c2VyczphdXRoIiwiZXhwIjoxNTg3ODE4NDI5fQ.anO3JR8-WYCozZ4_2-PQ2Ov9O38RaLP2RAzQIiZhteM\",\n    \"token_type\": \"bearer\"\n}\n

You can use this token to make authenticated requests as the user king.arthur@camelot.bt. We'll see how in the next section.

"},{"location":"usage/flow/#cookie-jwt","title":"Cookie + JWT","text":""},{"location":"usage/flow/#request_2","title":"Request","text":"cURLaxios
curl \\\n-v \\\n-H \"Content-Type: multipart/form-data\" \\\n-X POST \\\n-F \"username=king.arthur@camelot.bt\" \\\n-F \"password=guinevere\" \\\nhttp://localhost:8000/auth/cookie/login\n
const formData = new FormData();\nformData.set('username', 'king.arthur@camelot.bt');\nformData.set('password', 'guinevere');\naxios.post(\n    'http://localhost:8000/auth/cookie/login',\n    formData,\n    {\n        headers: {\n            'Content-Type': 'multipart/form-data',\n        },\n    },\n)\n.then((response) => console.log(response))\n.catch((error) => console.log(error));\n

Warning

Notice that we don't send it as a JSON payload here but with form data instead. Also, the email is provided by a field named username.

"},{"location":"usage/flow/#response_2","title":"Response","text":"

You'll get an empty response. However, the response will come with a Set-Cookie header (that's why we added the -v option in cURL to see them).

set-cookie: fastapiusersauth=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiYzYwNjBmMTEtNTM0OS00YTI0LThiNGEtYTJhODc1ZGM1Mzk1IiwiYXVkIjoiZmFzdGFwaS11c2VyczphdXRoIiwiZXhwIjoxNTg3ODE4OTQ3fQ.qNA4oPVYhoqrJIk-zvAyEfEVoEnP156G30H_SWEU0sU; HttpOnly; Max-Age=3600; Path=/; Secure\n

You can make authenticated requests as the user king.arthur@camelot.bt by setting a Cookie header with this cookie.

Tip

The cookie backend is more suited for browsers, as they handle them automatically. This means that if you make a login request in the browser, it will automatically store the cookie and automatically send it in subsequent requests.

"},{"location":"usage/flow/#3-get-my-profile","title":"3. Get my profile","text":"

Now that we can authenticate, we can get our own profile data. Depending on your authentication backend, the method to authenticate the request will vary. We'll stick with JWT from now on.

"},{"location":"usage/flow/#request_3","title":"Request","text":"cURLaxios
export TOKEN=\"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiNGZkMzQ3N2ItZWNjZi00ZWUzLThmN2QtNjhhZDcyMjYxNDc2IiwiYXVkIjoiZmFzdGFwaS11c2VyczphdXRoIiwiZXhwIjoxNTg3ODE4NDI5fQ.anO3JR8-WYCozZ4_2-PQ2Ov9O38RaLP2RAzQIiZhteM\";\ncurl \\\n-H \"Content-Type: application/json\" \\\n-H \"Authorization: Bearer $TOKEN\" \\\n-X GET \\\nhttp://localhost:8000/users/me\n
const TOKEN = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiNGZkMzQ3N2ItZWNjZi00ZWUzLThmN2QtNjhhZDcyMjYxNDc2IiwiYXVkIjoiZmFzdGFwaS11c2VyczphdXRoIiwiZXhwIjoxNTg3ODE4NDI5fQ.anO3JR8-WYCozZ4_2-PQ2Ov9O38RaLP2RAzQIiZhteM';\naxios.get(\n    'http://localhost:8000/users/me', {\n    headers: {\n        'Authorization': `Bearer ${TOKEN}`,\n    },\n})\n.then((response) => console.log(response))\n.catch((error) => console.log(error));\n
"},{"location":"usage/flow/#response_3","title":"Response","text":"

You'll get a JSON response looking like this:

{\n    \"id\": \"4fd3477b-eccf-4ee3-8f7d-68ad72261476\",\n    \"email\": \"king.arthur@camelot.bt\",\n    \"is_active\": true,\n    \"is_superuser\": false\n}\n

Tip

If you use one of the dependency callable to protect one of your own endpoint, you'll have to authenticate exactly in the same way.

"},{"location":"usage/flow/#4-update-my-profile","title":"4. Update my profile","text":"

We can also update our own profile. For example, we can change our password like this.

"},{"location":"usage/flow/#request_4","title":"Request","text":"cURLaxios
curl \\\n-H \"Content-Type: application/json\" \\\n-H \"Authorization: Bearer $TOKEN\" \\\n-X PATCH \\\n-d \"{\\\"password\\\": \\\"lancelot\\\"}\" \\\nhttp://localhost:8000/users/me\n
axios.patch(\n    'http://localhost:8000/users/me',\n    {\n        password: 'lancelot',\n    },\n    {\n        headers: {\n            'Authorization': `Bearer ${TOKEN}`,\n        },\n    },\n)\n.then((response) => console.log(response))\n.catch((error) => console.log(error));\n
"},{"location":"usage/flow/#response_4","title":"Response","text":"

You'll get a JSON response looking like this:

{\n    \"id\": \"4fd3477b-eccf-4ee3-8f7d-68ad72261476\",\n    \"email\": \"king.arthur@camelot.bt\",\n    \"is_active\": true,\n    \"is_superuser\": false\n}\n

Info

Once again, the user cannot set is_active or is_superuser itself. Only a superuser can do it by PATCHing the user.

"},{"location":"usage/flow/#5-become-a-superuser","title":"5. Become a superuser \ud83e\uddb8\ud83c\udffb\u200d\u2642\ufe0f","text":"

If you want to manage the users of your application, you'll have to become a superuser.

The very first superuser can only be set at database level: open it through a CLI or a GUI, find your user and set the is_superuser column/property to true.

"},{"location":"usage/flow/#51-get-the-profile-of-any-user","title":"5.1. Get the profile of any user","text":"

Now that you are a superuser, you can leverage the power of superuser routes. You can for example get the profile of any user in the database given its id.

"},{"location":"usage/flow/#request_5","title":"Request","text":"cURLaxios
curl \\\n-H \"Content-Type: application/json\" \\\n-H \"Authorization: Bearer $TOKEN\" \\\n-X GET \\\nhttp://localhost:8000/users/4fd3477b-eccf-4ee3-8f7d-68ad72261476\n
axios.get(\n    'http://localhost:8000/users/4fd3477b-eccf-4ee3-8f7d-68ad72261476', {\n    headers: {\n        'Authorization': `Bearer ${TOKEN}`,\n    },\n})\n.then((response) => console.log(response))\n.catch((error) => console.log(error));\n
"},{"location":"usage/flow/#response_5","title":"Response","text":"

You'll get a JSON response looking like this:

{\n    \"id\": \"4fd3477b-eccf-4ee3-8f7d-68ad72261476\",\n    \"email\": \"king.arthur@camelot.bt\",\n    \"is_active\": true,\n    \"is_superuser\": false\n}\n
"},{"location":"usage/flow/#51-update-any-user","title":"5.1. Update any user","text":"

We can now update the profile of any user. For example, we can promote it as superuser.

"},{"location":"usage/flow/#request_6","title":"Request","text":"cURLaxios
curl \\\n-H \"Content-Type: application/json\" \\\n-H \"Authorization: Bearer $TOKEN\" \\\n-X PATCH \\\n -d \"{\\\"is_superuser\\\": true}\" \\\nhttp://localhost:8000/users/4fd3477b-eccf-4ee3-8f7d-68ad72261476\n
axios.patch(\n    'http://localhost:8000/users/4fd3477b-eccf-4ee3-8f7d-68ad72261476',\n    {\n        is_superuser: true,\n    },\n    {\n        headers: {\n            'Authorization': `Bearer ${TOKEN}`,\n        },\n    },\n)\n.then((response) => console.log(response))\n.catch((error) => console.log(error));\n
"},{"location":"usage/flow/#response_6","title":"Response","text":"

You'll get a JSON response looking like this:

{\n    \"id\": \"4fd3477b-eccf-4ee3-8f7d-68ad72261476\",\n    \"email\": \"king.arthur@camelot.bt\",\n    \"is_active\": true,\n    \"is_superuser\": true\n}\n
"},{"location":"usage/flow/#52-delete-any-user","title":"5.2. Delete any user","text":"

Finally, we can delete a user.

"},{"location":"usage/flow/#request_7","title":"Request","text":"cURLaxios
curl \\\n-H \"Content-Type: application/json\" \\\n-H \"Authorization: Bearer $TOKEN\" \\\n-X DELETE \\\nhttp://localhost:8000/users/4fd3477b-eccf-4ee3-8f7d-68ad72261476\n
axios.delete(\n    'http://localhost:8000/users/4fd3477b-eccf-4ee3-8f7d-68ad72261476',\n    {\n        headers: {\n            'Authorization': `Bearer ${TOKEN}`,\n        },\n    },\n)\n.then((response) => console.log(response))\n.catch((error) => console.log(error));\n
"},{"location":"usage/flow/#response_7","title":"Response","text":"

You'll get an empty response.

"},{"location":"usage/flow/#6-logout","title":"6. Logout","text":"

We can also end the session.

"},{"location":"usage/flow/#request_8","title":"Request","text":"cURLaxios
curl \\\n-H \"Content-Type: application/json\" \\\n-H \"Cookie: fastapiusersauth=$TOKEN\" \\\n-X POST \\\nhttp://localhost:8000/auth/cookie/logout\n
axios.post('http://localhost:8000/auth/cookie/logout',\n    null,\n    {\n        headers: {\n            'Cookie': `fastapiusersauth=${TOKEN}`,\n        },\n    }\n)\n.then((response) => console.log(response))\n.catch((error) => console.log(error));\n
"},{"location":"usage/flow/#response_8","title":"Response","text":"

You'll get an empty response.

"},{"location":"usage/flow/#conclusion","title":"Conclusion","text":"

That's it! You now have a good overview of how you can manage the users through the API. Be sure to check the Routes page to have all the details about each endpoints.

"},{"location":"usage/routes/","title":"Routes","text":"

You'll find here the routes exposed by FastAPI Users. Note that you can also review them through the interactive API docs.

"},{"location":"usage/routes/#auth-router","title":"Auth router","text":"

Each authentication backend you generate a router for will produce the following routes. Take care about the prefix you gave it, especially if you have several backends.

"},{"location":"usage/routes/#post-login","title":"POST /login","text":"

Login a user against the method named name. Check the corresponding authentication method to view the success response.

Payload (application/x-www-form-urlencoded)

username=king.arthur@camelot.bt&password=guinevere\n

422 Validation Error

400 Bad Request

Bad credentials or the user is inactive.

{\n    \"detail\": \"LOGIN_BAD_CREDENTIALS\"\n}\n

400 Bad Request

The user is not verified.

{\n    \"detail\": \"LOGIN_USER_NOT_VERIFIED\"\n}\n
"},{"location":"usage/routes/#post-logout","title":"POST /logout","text":"

Logout the authenticated user against the method named name. Check the corresponding authentication method to view the success response.

401 Unauthorized

Missing token or inactive user.

204 No content

The logout process was successful.

"},{"location":"usage/routes/#register-router","title":"Register router","text":""},{"location":"usage/routes/#post-register","title":"POST /register","text":"

Register a new user. Will call the on_after_register handler on successful registration.

Payload

{\n    \"email\": \"king.arthur@camelot.bt\",\n    \"password\": \"guinevere\"\n}\n

201 Created

{\n    \"id\": \"57cbb51a-ab71-4009-8802-3f54b4f2e23\",\n    \"email\": \"king.arthur@camelot.bt\",\n    \"is_active\": true,\n    \"is_superuser\": false\n}\n

422 Validation Error

400 Bad Request

A user already exists with this email.

{\n    \"detail\": \"REGISTER_USER_ALREADY_EXISTS\"\n}\n

400 Bad Request

Password validation failed.

{\n    \"detail\": {\n        \"code\": \"REGISTER_INVALID_PASSWORD\",\n        \"reason\": \"Password should be at least 3 characters\"\n    }\n}\n
"},{"location":"usage/routes/#reset-password-router","title":"Reset password router","text":""},{"location":"usage/routes/#post-forgot-password","title":"POST /forgot-password","text":"

Request a reset password procedure. Will generate a temporary token and call the on_after_forgot_password handler if the user exists.

To prevent malicious users from guessing existing users in your database, the route will always return a 202 Accepted response, even if the user requested does not exist.

Payload

{\n    \"email\": \"king.arthur@camelot.bt\"\n}\n

202 Accepted

"},{"location":"usage/routes/#post-reset-password","title":"POST /reset-password","text":"

Reset a password. Requires the token generated by the /forgot-password route.

Payload

{\n    \"token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiOTIyMWZmYzktNjQwZi00MzcyLTg2ZDMtY2U2NDJjYmE1NjAzIiwiYXVkIjoiZmFzdGFwaS11c2VyczphdXRoIiwiZXhwIjoxNTcxNTA0MTkzfQ.M10bjOe45I5Ncu_uXvOmVV8QxnL-nZfcH96U90JaocI\",\n    \"password\": \"merlin\"\n}\n

200 OK

422 Validation Error

400 Bad Request

Bad or expired token.

{\n    \"detail\": \"RESET_PASSWORD_BAD_TOKEN\"\n}\n

400 Bad Request

Password validation failed.

{\n    \"detail\": {\n        \"code\": \"RESET_PASSWORD_INVALID_PASSWORD\",\n        \"reason\": \"Password should be at least 3 characters\"\n    }\n}\n
"},{"location":"usage/routes/#verify-router","title":"Verify router","text":""},{"location":"usage/routes/#post-request-verify-token","title":"POST /request-verify-token","text":"

Request a user to verify their e-mail. Will generate a temporary token and call the on_after_request_verify handler if the user exists, active and not already verified.

To prevent malicious users from guessing existing users in your database, the route will always return a 202 Accepted response, even if the user requested does not exist, not active or already verified.

Payload

{\n    \"email\": \"king.arthur@camelot.bt\"\n}\n

202 Accepted

"},{"location":"usage/routes/#post-verify","title":"POST /verify","text":"

Verify a user. Requires the token generated by the /request-verify-token route. Will call the call the on_after_verify handler on success.

Payload

{\n    \"token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiOTIyMWZmYzktNjQwZi00MzcyLTg2ZDMtY2U2NDJjYmE1NjAzIiwiYXVkIjoiZmFzdGFwaS11c2VyczphdXRoIiwiZXhwIjoxNTcxNTA0MTkzfQ.M10bjOe45I5Ncu_uXvOmVV8QxnL-nZfcH96U90JaocI\"\n}\n

200 OK

422 Validation Error

400 Bad Request

Bad token, not existing user or not the e-mail currently set for the user.

{\n    \"detail\": \"VERIFY_USER_BAD_TOKEN\"\n}\n

400 Bad Request

The user is already verified.

{\n    \"detail\": \"VERIFY_USER_ALREADY_VERIFIED\"\n}\n
"},{"location":"usage/routes/#oauth-router","title":"OAuth router","text":"

Each OAuth router you define will expose the two following routes.

"},{"location":"usage/routes/#get-authorize","title":"GET /authorize","text":"

Return the authorization URL for the OAuth service where you should redirect your user.

Query parameters

  • scopes: Optional list of scopes to ask for. Expected format: scopes=a&scopes=b.

200 OK

{\n    \"authorization_url\": \"https://www.tintagel.bt/oauth/authorize?client_id=CLIENT_ID&scopes=a+b&redirect_uri=https://www.camelot.bt/oauth/callback\"\n}\n
"},{"location":"usage/routes/#get-callback","title":"GET /callback","text":"

Handle the OAuth callback.

Query parameters

  • code: OAuth callback code.
  • state: State token.
  • error: OAuth error.

Depending on the situation, several things can happen:

  • The OAuth account exists in database and is linked to a user:
    • OAuth account is updated in database with fresh access token.
    • The user is authenticated following the chosen authentication method.
  • The OAuth account doesn't exist in database but a user with the same email address exists:
    • By default, an HTTP 400 error is raised.
    • If the associate_by_email flag is set to True on the router declaration, OAuth account is linked to the user. The user is authenticated following the chosen authentication method.
  • The OAuth account doesn't exist in database and no user with the email address exists:
    • A new user is created and linked to the OAuth account.
    • The user is authenticated following the chosen authentication method.

400 Bad Request

Invalid token.

400 Bad Request

The OAuth provider didn't return an e-mail address. Make sure this provider return e-mail address through their API and you have asked for the required scope.

{\n    \"detail\": \"OAUTH_NOT_AVAILABLE_EMAIL\"\n}\n

400 Bad Request

Another user with the same e-mail address already exists.

{\n    \"detail\": \"OAUTH_USER_ALREADY_EXISTS\"\n}\n

400 Bad Request

User is inactive.

{\n    \"detail\": \"LOGIN_BAD_CREDENTIALS\"\n}\n
"},{"location":"usage/routes/#oauth-association-router","title":"OAuth association router","text":"

Each OAuth association router you define will expose the two following routes.

"},{"location":"usage/routes/#get-authorize_1","title":"GET /authorize","text":"

Return the authorization URL for the OAuth service where you should redirect your user.

Query parameters

  • scopes: Optional list of scopes to ask for. Expected format: scopes=a&scopes=b.

401 Unauthorized

Missing token or inactive user.

200 OK

{\n    \"authorization_url\": \"https://www.tintagel.bt/oauth/authorize?client_id=CLIENT_ID&scopes=a+b&redirect_uri=https://www.camelot.bt/oauth/callback\"\n}\n
"},{"location":"usage/routes/#get-callback_1","title":"GET /callback","text":"

Handle the OAuth callback and add the OAuth account to the current authenticated active user.

Query parameters

  • code: OAuth callback code.
  • state: State token.
  • error: OAuth error.

401 Unauthorized

Missing token or inactive user.

400 Bad Request

Invalid token.

400 Bad Request

The OAuth provider didn't return an e-mail address. Make sure this provider return e-mail address through their API and you have asked for the required scope.

{\n    \"detail\": \"OAUTH_NOT_AVAILABLE_EMAIL\"\n}\n

200 OK

{\n    \"id\": \"57cbb51a-ab71-4009-8802-3f54b4f2e23\",\n    \"email\": \"king.arthur@tintagel.bt\",\n    \"is_active\": true,\n    \"is_superuser\": false,\n    \"oauth_accounts\": [\n        {\n            \"id\": \"6c98caf5-9bc5-4c4f-8a45-a0ae0c40cd77\",\n            \"oauth_name\": \"TINTAGEL\",\n            \"access_token\": \"ACCESS_TOKEN\",\n            \"expires_at\": \"1641040620\",\n            \"account_id\": \"king_arthur_tintagel\",\n            \"account_email\": \"king.arthur@tintagel.bt\"\n        }\n    ]\n}\n
"},{"location":"usage/routes/#users-router","title":"Users router","text":""},{"location":"usage/routes/#get-me","title":"GET /me","text":"

Return the current authenticated active user.

200 OK

{\n    \"id\": \"57cbb51a-ab71-4009-8802-3f54b4f2e23\",\n    \"email\": \"king.arthur@camelot.bt\",\n    \"is_active\": true,\n    \"is_superuser\": false\n}\n

401 Unauthorized

Missing token or inactive user.

"},{"location":"usage/routes/#patch-me","title":"PATCH /me","text":"

Update the current authenticated active user.

Payload

{\n    \"email\": \"king.arthur@tintagel.bt\",\n    \"password\": \"merlin\"\n}\n

200 OK

{\n    \"id\": \"57cbb51a-ab71-4009-8802-3f54b4f2e23\",\n    \"email\": \"king.arthur@tintagel.bt\",\n    \"is_active\": true,\n    \"is_superuser\": false\n}\n

401 Unauthorized

Missing token or inactive user.

400 Bad Request

Password validation failed.

{\n    \"detail\": {\n        \"code\": \"UPDATE_USER_INVALID_PASSWORD\",\n        \"reason\": \"Password should be at least 3 characters\"\n    }\n}\n

400 Bad Request

A user with this email already exists.

{\n    \"detail\": \"UPDATE_USER_EMAIL_ALREADY_EXISTS\"\n}\n

422 Validation Error

"},{"location":"usage/routes/#get-user_id","title":"GET /{user_id}","text":"

Return the user with id user_id.

200 OK

{\n    \"id\": \"57cbb51a-ab71-4009-8802-3f54b4f2e23\",\n    \"email\": \"king.arthur@camelot.bt\",\n    \"is_active\": true,\n    \"is_superuser\": false\n}\n

401 Unauthorized

Missing token or inactive user.

403 Forbidden

Not a superuser.

404 Not found

The user does not exist.

"},{"location":"usage/routes/#patch-user_id","title":"PATCH /{user_id}","text":"

Update the user with id user_id.

Payload

{\n    \"email\": \"king.arthur@tintagel.bt\",\n    \"password\": \"merlin\",\n    \"is_active\": false,\n    \"is_superuser\": true\n}\n

200 OK

{\n    \"id\": \"57cbb51a-ab71-4009-8802-3f54b4f2e23\",\n    \"email\": \"king.arthur@camelot.bt\",\n    \"is_active\": false,\n    \"is_superuser\": true\n}\n

401 Unauthorized

Missing token or inactive user.

403 Forbidden

Not a superuser.

404 Not found

The user does not exist.

400 Bad Request

Password validation failed.

{\n    \"detail\": {\n        \"code\": \"UPDATE_USER_INVALID_PASSWORD\",\n        \"reason\": \"Password should be at least 3 characters\"\n    }\n}\n

400 Bad Request

A user with this email already exists.

{\n    \"detail\": \"UPDATE_USER_EMAIL_ALREADY_EXISTS\"\n}\n

"},{"location":"usage/routes/#delete-user_id","title":"DELETE /{user_id}","text":"

Delete the user with id user_id.

204 No content

401 Unauthorized

Missing token or inactive user.

403 Forbidden

Not a superuser.

404 Not found

The user does not exist.

"}]} \ No newline at end of file diff --git a/dev/sitemap.xml.gz b/dev/sitemap.xml.gz index 9c2e83bd..bac9f054 100644 Binary files a/dev/sitemap.xml.gz and b/dev/sitemap.xml.gz differ diff --git a/dev/src/db_beanie_oauth.py b/dev/src/db_beanie_oauth.py index e7179a6d..b1786869 100644 --- a/dev/src/db_beanie_oauth.py +++ b/dev/src/db_beanie_oauth.py @@ -1,5 +1,3 @@ -from typing import List - import motor.motor_asyncio from beanie import Document from fastapi_users.db import BaseOAuthAccount, BeanieBaseUser, BeanieUserDatabase @@ -17,7 +15,7 @@ class OAuthAccount(BaseOAuthAccount): class User(BeanieBaseUser, Document): - oauth_accounts: List[OAuthAccount] = Field(default_factory=list) + oauth_accounts: list[OAuthAccount] = Field(default_factory=list) async def get_user_db(): diff --git a/dev/src/db_sqlalchemy.py b/dev/src/db_sqlalchemy.py index 1ff831d3..1bff0fd6 100644 --- a/dev/src/db_sqlalchemy.py +++ b/dev/src/db_sqlalchemy.py @@ -1,4 +1,4 @@ -from typing import AsyncGenerator +from collections.abc import AsyncGenerator from fastapi import Depends from fastapi_users.db import SQLAlchemyBaseUserTableUUID, SQLAlchemyUserDatabase diff --git a/dev/src/db_sqlalchemy_access_tokens.py b/dev/src/db_sqlalchemy_access_tokens.py index 97fc20bd..79c75816 100644 --- a/dev/src/db_sqlalchemy_access_tokens.py +++ b/dev/src/db_sqlalchemy_access_tokens.py @@ -1,4 +1,4 @@ -from typing import AsyncGenerator +from collections.abc import AsyncGenerator from fastapi import Depends from fastapi_users.db import SQLAlchemyBaseUserTableUUID, SQLAlchemyUserDatabase diff --git a/dev/src/db_sqlalchemy_oauth.py b/dev/src/db_sqlalchemy_oauth.py index 827dc38a..93a38e11 100644 --- a/dev/src/db_sqlalchemy_oauth.py +++ b/dev/src/db_sqlalchemy_oauth.py @@ -1,4 +1,4 @@ -from typing import AsyncGenerator, List +from collections.abc import AsyncGenerator from fastapi import Depends from fastapi_users.db import ( @@ -21,7 +21,7 @@ class OAuthAccount(SQLAlchemyBaseOAuthAccountTableUUID, Base): class User(SQLAlchemyBaseUserTableUUID, Base): - oauth_accounts: Mapped[List[OAuthAccount]] = relationship( + oauth_accounts: Mapped[list[OAuthAccount]] = relationship( "OAuthAccount", lazy="joined" ) diff --git a/dev/usage/current-user/index.html b/dev/usage/current-user/index.html index 07337a0c..acd695bf 100644 --- a/dev/usage/current-user/index.html +++ b/dev/usage/current-user/index.html @@ -18,7 +18,7 @@ - + diff --git a/dev/usage/flow/index.html b/dev/usage/flow/index.html index dab64a9b..7dc095c2 100644 --- a/dev/usage/flow/index.html +++ b/dev/usage/flow/index.html @@ -18,7 +18,7 @@ - + diff --git a/dev/usage/routes/index.html b/dev/usage/routes/index.html index 52072d4b..86e4af5e 100644 --- a/dev/usage/routes/index.html +++ b/dev/usage/routes/index.html @@ -18,7 +18,7 @@ - +