Update documentation for DB strategy and fix DB dependencies versions

This commit is contained in:
François Voron
2022-01-03 11:26:14 +01:00
parent 1ede89933a
commit c1083f77b5
9 changed files with 215 additions and 5 deletions

View File

@@ -43,7 +43,7 @@ Add quickly a registration and authentication system to your [FastAPI](https://f
* [X] [ormar](https://collerek.github.io/ormar/) backend included
* [X] Multiple customizable authentication backends
* [X] Transports: Authorization header, Cookie
* [X] Strategies: JWT, Redis
* [X] Strategies: JWT, Database, Redis
* [X] Full OpenAPI schema support, even with several authentication backends
## 📚 Discover my book: *Building Data Science Applications with FastAPI*

View File

@@ -57,6 +57,19 @@ The token is self-contained in a JSON Web Token.
➡️ Use it if you want to get up-and-running quickly.
#### [Database](strategies/database.md)
The token is stored in a table (or collection) in your database.
!!! tip "Pros and cons"
* ✅ Secure and performant.
* ✅ Tokens can be invalidated server-side by removing them from the database.
* ✅ Highly customizable: add your own fields, create an API to retrieve the active sessions of your users, etc.
* ❌ Configuration is a bit more complex.
➡️ Use it if you want maximum flexibility in your token management.
#### [Redis](strategies/redis.md)
The token is stored in a Redis key-store.
@@ -64,7 +77,7 @@ The token is stored in a Redis key-store.
!!! tip "Pros and cons"
* ✅ Secure and performant.
* ✅ Tokens can be invalidated server-side by removing tokens from Redis.
* ✅ Tokens can be invalidated server-side by removing them from Redis.
* ❌ A Redis server is needed.
➡️ Use it if you want maximum performance while being able to invalidate tokens.

View File

@@ -0,0 +1,84 @@
# Database
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 retrive this token from the database to get the corresponding user id.
## Configuration
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](../../overview.md#database-adapters).
### Model
You should define an `AccessToken` Pydantic model inheriting from `BaseAccessToken`.
```py
from fastapi_users.authentication.strategy.db import BaseAccessToken
class AccessToken(BaseAccessToken):
pass
```
It is structured like this:
* `token` (`str`) Unique identifier of the token. It's generated automatically upon login by the strategy.
* `user_id` (`UUID4`) User id. of the user associated to this token.
* `created_at` (`datetime`) Date and time of creation of the token. It's used to determine if the token is expired or not.
### Database adapter
=== "SQLAlchemy"
```py hl_lines="4-7 10 21-22 31 38-39"
--8<-- "docs/src/db_sqlalchemy_access_tokens.py"
```
=== "Tortoise ORM"
With Tortoise ORM, you need to define a proper Tortoise model for `AccessToken` and manually specify the user foreign key. Besides, you need to modify the Pydantic model a bit so that it works well with this Tortoise model.
=== ":octicons-file-code-16: model.py"
```py hl_lines="2 4 31-38"
--8<-- "docs/src/db_tortoise_access_tokens_model.py"
```
=== ":octicons-file-code-16: adapter.py"
```py hl_lines="2 4 13-14"
--8<-- "docs/src/db_tortoise_access_tokens_adapter.py"
```
=== "MongoDB"
```py hl_lines="3 5 13 20-21"
--8<-- "docs/src/db_mongodb_access_tokens.py"
```
### Strategy
```py
from fastapi import Depends
from fastapi_users.authentication.db import AccessTokenDatabase, DatabaseStrategy
from .models import AccessToken, UserCreate, UserDB
def get_database_strategy(
access_token_db: AccessTokenDatabase[AccessToken] = Depends(get_access_token_db),
) -> DatabaseStrategy[UserCreate, UserDB, AccessToken]:
return DatabaseStrategy(access_token_db, lifetime_seconds=3600)
```
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.
!!! tip "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.
## Logout
On logout, this strategy will delete the token from the database.

View File

@@ -0,0 +1,21 @@
import motor.motor_asyncio
from fastapi_users.db import MongoDBUserDatabase
from fastapi_users_db_mongodb.access_token import MongoDBAccessTokenDatabase
from .models import AccessToken, UserDB
DATABASE_URL = "mongodb://localhost:27017"
client = motor.motor_asyncio.AsyncIOMotorClient(
DATABASE_URL, uuidRepresentation="standard"
)
db = client["database_name"]
users_collection = db["users"]
access_tokens_collection = db["access_tokens"]
async def get_user_db():
yield MongoDBUserDatabase(UserDB, users_collection)
async def get_access_token_db():
yield MongoDBAccessTokenDatabase(AccessToken, access_tokens_collection)

View File

@@ -0,0 +1,39 @@
import databases
import sqlalchemy
from fastapi_users.db import SQLAlchemyBaseUserTable, SQLAlchemyUserDatabase
from fastapi_users_db_sqlalchemy.access_token import (
SQLAlchemyAccessTokenDatabase,
SQLAlchemyBaseAccessTokenTable,
)
from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base
from .models import AccessToken, UserDB
DATABASE_URL = "sqlite:///./test.db"
database = databases.Database(DATABASE_URL)
Base: DeclarativeMeta = declarative_base()
class UserTable(Base, SQLAlchemyBaseUserTable):
pass
class AccessTokenTable(SQLAlchemyBaseAccessTokenTable, Base):
pass
engine = sqlalchemy.create_engine(
DATABASE_URL, connect_args={"check_same_thread": False}
)
Base.metadata.create_all(engine)
users = UserTable.__table__
access_tokens = AccessTokenTable.__table__
async def get_user_db():
yield SQLAlchemyUserDatabase(UserDB, database, users)
async def get_access_token_db():
yield SQLAlchemyAccessTokenDatabase(AccessToken, database, access_tokens)

View File

@@ -0,0 +1,14 @@
from fastapi_users.db import TortoiseUserDatabase
from fastapi_users_db_tortoise.access_token import TortoiseAccessTokenDatabase
from .models import AccessToken, AccessTokenModel, UserDB, UserModel
DATABASE_URL = "sqlite://./test.db"
async def get_user_db():
yield TortoiseUserDatabase(UserDB, UserModel)
async def get_access_token_db():
yield TortoiseAccessTokenDatabase(AccessToken, AccessTokenModel)

View File

@@ -0,0 +1,38 @@
from fastapi_users import models
from fastapi_users.authentication.strategy.db.models import BaseAccessToken
from fastapi_users.db import TortoiseBaseUserModel
from fastapi_users_db_tortoise.access_token import TortoiseBaseAccessTokenModel
from tortoise import fields
from tortoise.contrib.pydantic import PydanticModel
class User(models.BaseUser):
pass
class UserCreate(models.BaseUserCreate):
pass
class UserUpdate(models.BaseUserUpdate):
pass
class UserModel(TortoiseBaseUserModel):
pass
class UserDB(User, models.BaseUserDB, PydanticModel):
class Config:
orm_mode = True
orig_model = UserModel
class AccessTokenModel(TortoiseBaseAccessTokenModel):
user = fields.ForeignKeyField("models.UserModel", related_name="access_tokens")
class AccessToken(BaseAccessToken, PydanticModel):
class Config:
orm_mode = True
orig_model = AccessTokenModel

View File

@@ -69,6 +69,7 @@ nav:
- configuration/authentication/transports/cookie.md
- configuration/authentication/transports/bearer.md
- Strategies:
- configuration/authentication/strategies/database.md
- configuration/authentication/strategies/jwt.md
- configuration/authentication/strategies/redis.md
- configuration/authentication/backend.md

View File

@@ -34,13 +34,13 @@ requires = [
[tool.flit.metadata.requires-extra]
sqlalchemy = [
"fastapi-users-db-sqlalchemy >=1.0.0",
"fastapi-users-db-sqlalchemy >=1.1.0",
]
mongodb = [
"fastapi-users-db-mongodb >=1.0.0",
"fastapi-users-db-mongodb >=1.1.0",
]
tortoise-orm = [
"fastapi-users-db-tortoise >=1.0.0",
"fastapi-users-db-tortoise >=1.1.0",
]
ormar = [
"fastapi-users-db-ormar >=1.0.0",