Revamp authentication routes structure (#201)

* Fix #68: use makefun to generate dynamic dependencies

* Remove every Starlette imports

* Split every routers and remove event handlers

* Make users router optional

* Pass after_update handler to get_users_router

* Update documentation

* Remove test file

* Write migration doc for splitted routers
This commit is contained in:
François Voron
2020-05-24 10:18:01 +02:00
committed by GitHub
parent 0a0dcadfdc
commit 7721f8dcc1
48 changed files with 1633 additions and 1167 deletions

View File

@@ -18,16 +18,6 @@ auth_backends.append(cookie_authentication)
As you can see, instantiation is quite simple. You just have to define a constant `SECRET` which is used to encode the token and the lifetime of the cookie (in seconds).
You can optionally define the `name` which will be used to generate its [`/login` route](../../usage/routes.md#post-loginname). **Defaults to `cookie`**.
```py
cookie_authentication = CookieAuthentication(
secret=SECRET,
lifetime_seconds=3600,
name="my-cookie",
)
```
You can also define the parameters for the generated cookie:
* `cookie_name` (`fastapiusersauth`): Name of the cookie.
@@ -36,6 +26,17 @@ You can also define the parameters for the generated cookie:
* `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.
!!! tip
You can also optionally define the `name`. It's useful in the case you wish to have several backends of the same class. Each backend should have a unique name. **Defaults to `cookie`**.
```py
cookie_authentication = CookieAuthentication(
secret=SECRET,
lifetime_seconds=3600,
name="my-cookie",
)
```
!!! tip
The value of the cookie is actually a JWT. This authentication backend shares most of its logic with the [JWT](./jwt.md) one.
@@ -61,4 +62,4 @@ This method expects that you provide a valid cookie in the headers.
## Next steps
We will now configure the main **FastAPI Users** object that will expose the [API router](../router.md).
We will now configure the main **FastAPI Users** object that will expose the [routers](../routers/index.md).

View File

@@ -8,9 +8,7 @@ You can have **several** authentication methods, e.g. a cookie authentication fo
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.
Each defined method will generate a [`/login/{name}`](../../usage/routes.md#post-loginname) route where `name` is defined on the authentication method object.
Each defined method will generate a [`/logout/{name}`](../../usage/routes.md#post-logoutname) route where `name` is defined on the authentication method object.
For each backend, you'll be able to add a router with the corresponding `/login` and `/logout` (if applicable routes). More on this in the [routers documentation](routers/index.md).
## Provided methods

View File

@@ -18,15 +18,16 @@ auth_backends.append(jwt_authentication)
As you can see, instantiation is quite simple. You just have to define a constant `SECRET` which is used to encode the token and the lifetime of token (in seconds).
You can also optionally define the `name` which will be used to generate its [`/login` route](../../usage/routes.md#post-loginname). **Defaults to `jwt`**.
!!! tip
You can also optionally define the `name`. It's useful in the case you wish to have several backends of the same class. Each backend should have a unique name. **Defaults to `jwt`**.
```py
jwt_authentication = JWTAuthentication(
secret=SECRET,
lifetime_seconds=3600,
name="my-jwt",
)
```
```py
jwt_authentication = JWTAuthentication(
secret=SECRET,
lifetime_seconds=3600,
name="my-jwt",
)
```
## Login
@@ -43,11 +44,7 @@ This method will return a JWT token upon successful login:
## Logout
This method is not applicable to this backend and won't do anything.
!!! success "`202 Accepted`"
> Check documentation about [logout route](../../usage/routes.md#post-logoutname).
This backend does not provide a logout method (a JWT is valid until it expires).
## Authentication
@@ -59,4 +56,4 @@ curl http://localhost:9000/protected-route -H'Authorization: Bearer eyJ0eXAiOiJK
## Next steps
We will now configure the main **FastAPI Users** object that will expose the [API router](../router.md).
We will now configure the main **FastAPI Users** object that will expose the [routers](../routers/index.md).

View File

@@ -121,12 +121,43 @@ google_oauth_client = GoogleOAuth2("CLIENT_ID", "CLIENT_SECRET")
app = FastAPI()
fastapi_users = FastAPIUsers(
user_db, auth_backends, User, UserCreate, UserUpdate, UserDB, SECRET,
user_db, auth_backends, User, UserCreate, UserUpdate, UserDB
)
google_oauth_router = fastapi_users.get_oauth_router(google_oauth_client, SECRET)
app.include_router(google_oauth_router, prefix="/google-oauth", tags=["users"])
app.include_router(google_oauth_router, prefix="/auth/google", tags=["auth"])
```
### After register
You can provide a custom function to be called after a successful registration. It is called with **two argument**: the **user** that has just registered, and the original **`Request` object**.
Typically, you'll want to **send a welcome e-mail** or add it to your marketing analytics pipeline.
You can define it as an `async` or standard method.
Example:
```py
from fastapi import FastAPI
from fastapi_users import FastAPIUsers
from httpx_oauth.clients.google import GoogleOAuth2
def on_after_register(user: UserDB, request: Request):
print(f"User {user.id} has registered.")
google_oauth_client = GoogleOAuth2("CLIENT_ID", "CLIENT_SECRET")
app = FastAPI()
fastapi_users = FastAPIUsers(
user_db, auth_backends, User, UserCreate, UserUpdate, UserDB
)
google_oauth_router = fastapi_users.get_oauth_router(google_oauth_client, SECRET, after_register=on_after_register)
app.include_router(google_oauth_router, prefix="/auth/google", tags=["auth"])
```
### Full example

View File

@@ -1,101 +0,0 @@
# Router
We're almost there! The last step is to configure the `FastAPIUsers` object that will wire the database adapter, the authentication class and the user models to expose the FastAPI router.
## Configure `FastAPIUsers`
Configure `FastAPIUsers` object with all the elements we defined before. More precisely:
* `db`: Database adapter instance.
* `auth_backends`: List of authentication backends. See [Authentication](./authentication/index.md).
* `user_model`: Pydantic model of a user.
* `user_create_model`: Pydantic model for creating a user.
* `user_update_model`: Pydantic model for updating a user.
* `user_db_model`: Pydantic model of a DB representation of a user.
* `reset_password_token_secret`: Secret to encode reset password token.
* `reset_password_token_lifetime_seconds`: Lifetime of reset password token in seconds. Default to one hour.
```py
from fastapi_users import FastAPIUsers
fastapi_users = FastAPIUsers(
user_db,
auth_backends,
User,
UserCreate,
UserUpdate,
UserDB,
SECRET,
)
```
And then, include the router in the FastAPI app:
```py
app = FastAPI()
app.include_router(fastapi_users.router, prefix="/users", tags=["users"])
```
## Event handlers
In order to be as unopinionated as possible, we expose decorators that allow you to plug your own logic after some actions. You can have several handlers per event.
### After register
This event handler is called after a successful registration. It is called with **two argument**: the **user** that has just registered, and the original **`Request` object**.
Typically, you'll want to **send a welcome e-mail** or add it to your marketing analytics pipeline.
You can define it as an `async` or standard method.
Example:
```py
@fastapi_users.on_after_register()
def on_after_register(user: User, request: Request):
print(f"User {user.id} has registered.")
```
### After forgot password
This event handler is called after a successful forgot password request. It is called with **three arguments**:
* The **user** which has requested to reset their password.
* A ready-to-use **JWT token** that will be accepted by the reset password route.
* The original **`Request` object**.
Typically, you'll want to **send an e-mail** with the link (and the token) that allows the user to reset their password.
You can define it as an `async` or standard method.
Example:
```py
@fastapi_users.on_after_forgot_password()
def on_after_forgot_password(user: User, token: str, request: Request):
print(f"User {user.id} has forgot their password. Reset token: {token}")
```
### After update
This event handler is called after a successful update user request. It is called with **three arguments**:
* The **user** which was updated.
* The dictionary containing the updated fields.
* The original **`Request` object**.
It may be useful if you wish for example update your user in a data analytics or customer success platform.
You can define it as an `async` or standard method.
Example:
```py
@fastapi_users.on_after_update()
def on_after_update(user: User, updated_user_data: Dict[str, Any], request: Request):
print(f"User {user.id} has been updated with the following data: {updated_user_data}")
```
## Next steps
Check out a [full example](full_example.md) that will show you the big picture.

View File

@@ -0,0 +1,33 @@
# Auth router
The auth router will generate `/login` and `/logout` (if applicable) routes for a given [authentication backend](../authentication/index.md).
Check the [routes usage](../../usage/routes.md) to learn how to use them.
## Setup
```py
from fastapi import FastAPI
from fastapi_users import FastAPIUsers
from fastapi_users.authentication import JWTAuthentication
SECRET = "SECRET"
jwt_authentication = JWTAuthentication(secret=SECRET, lifetime_seconds=3600))
fastapi_users = FastAPIUsers(
user_db,
[jwt_authentication],
User,
UserCreate,
UserUpdate,
UserDB,
)
app = FastAPI()
app.include_router(
fastapi_users.get_auth_router(jwt_authentication),
prefix="/auth/jwt",
tags=["auth"],
)
```

View File

@@ -0,0 +1,39 @@
# Routers
We're almost there! The last step is to configure the `FastAPIUsers` object that will wire the database adapter, the authentication classes and let us generate the actual **API routes**.
## Configure `FastAPIUsers`
Configure `FastAPIUsers` object with all the elements we defined before. More precisely:
* `db`: Database adapter instance.
* `auth_backends`: List of authentication backends. See [Authentication](../authentication/index.md).
* `user_model`: Pydantic model of a user.
* `user_create_model`: Pydantic model for creating a user.
* `user_update_model`: Pydantic model for updating a user.
* `user_db_model`: Pydantic model of a DB representation of a user.
```py
from fastapi_users import FastAPIUsers
fastapi_users = FastAPIUsers(
user_db,
auth_backends,
User,
UserCreate,
UserUpdate,
UserDB,
)
```
## Available routers
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](./auth.md): Provides `/login` and `/logout` routes for a given [authentication backend](../authentication/index.md).
* [Register router](./register.md): Provides `/register` routes to allow a user to create a new account.
* [Reset password router](./reset.md): Provides `/forgot-password` and `/reset-password` routes to allow a user to reset its password.
* [Users router](./users.md): Provides routes to manage users.
* [OAuth router](../oauth.md): 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.

View File

@@ -0,0 +1,54 @@
# Register routes
The register router will generate a `/register` route to allow a user to create a new account.
Check the [routes usage](../../usage/routes.md) to learn how to use them.
## Setup
```py
from fastapi import FastAPI
from fastapi_users import FastAPIUsers
from fastapi_users.authentication import JWTAuthentication
SECRET = "SECRET"
jwt_authentication = JWTAuthentication(secret=SECRET, lifetime_seconds=3600))
fastapi_users = FastAPIUsers(
user_db,
[jwt_authentication],
User,
UserCreate,
UserUpdate,
UserDB,
)
app = FastAPI()
app.include_router(
fastapi_users.get_register_router(),
prefix="/auth",
tags=["auth"],
)
```
## After register
You can provide a custom function to be called after a successful registration. It is called with **two argument**: the **user** that has just registered, and the original **`Request` object**.
Typically, you'll want to **send a welcome e-mail** or add it to your marketing analytics pipeline.
You can define it as an `async` or standard method.
Example:
```py
def on_after_register(user: UserDB, request: Request):
print(f"User {user.id} has registered.")
app.include_router(
fastapi_users.get_register_router(on_after_register),
prefix="/auth",
tags=["auth"],
)
```

View File

@@ -0,0 +1,59 @@
# Reset password router
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](../../usage/routes.md) to learn how to use them.
## Setup
```py
from fastapi import FastAPI
from fastapi_users import FastAPIUsers
fastapi_users = FastAPIUsers(
user_db,
auth_backends,
User,
UserCreate,
UserUpdate,
UserDB,
)
app = FastAPI()
app.include_router(
fastapi_users.get_reset_password_router("SECRET"),
prefix="/auth",
tags=["auth"],
)
```
Parameters:
* `reset_password_token_secret`: Secret to encode reset password token.
* `reset_password_token_lifetime_seconds`: Lifetime of reset password token. **Defaults to 3600**.
* `after_forgot_password`: Optional function called after a successful forgot password request. See below.
## After forgot password
You can provide a custom function to be called after a successful forgot password request. It is called with **three arguments**:
* The **user** which has requested to reset their password.
* A ready-to-use **JWT token** that will be accepted by the reset password route.
* The original **`Request` object**.
Typically, you'll want to **send an e-mail** with the link (and the token) that allows the user to reset their password.
You can define it as an `async` or standard method.
Example:
```py
def on_after_forgot_password(user: UserDB, token: str, request: Request):
print(f"User {user.id} has forgot their password. Reset token: {token}")
app.include_router(
fastapi_users.get_reset_password_router("SECRET", after_forgot_password=on_after_forgot_password),
prefix="/auth",
tags=["auth"],
)
```

View File

@@ -0,0 +1,51 @@
# Users router
This router provides routes to manage users. Check the [routes usage](../../usage/routes.md) to learn how to use them.
## Setup
```py
from fastapi import FastAPI
from fastapi_users import FastAPIUsers
fastapi_users = FastAPIUsers(
user_db,
auth_backends,
User,
UserCreate,
UserUpdate,
UserDB,
)
app = FastAPI()
app.include_router(
fastapi_users.get_users_router(),
prefix="/users",
tags=["users"],
)
```
## After update
You can provide a custom function to be called after a successful update user request. It is called with **three arguments**:
* The **user** which was updated.
* The dictionary containing the updated fields.
* The original **`Request` object**.
It may be useful if you wish for example update your user in a data analytics or customer success platform.
You can define it as an `async` or standard method.
Example:
```py
def on_after_update(user: UserDB, updated_user_data: Dict[str, Any], request: Request):
print(f"User {user.id} has been updated with the following data: {updated_user_data}")
app.include_router(
fastapi_users.get_users_router(on_after_update),
prefix="/users",
tags=["users"],
)
```

View File

@@ -80,3 +80,71 @@ db.getCollection('users').find().forEach(function(user) {
db.getCollection('users').update({_id: user._id}, [{$set: {id: uuid}}]);
});
```
## Splitted routers
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.
### Before
```py
jwt_authentication = JWTAuthentication(secret=SECRET, lifetime_seconds=3600)
app = FastAPI()
fastapi_users = FastAPIUsers(
user_db, [jwt_authentication], User, UserCreate, UserUpdate, UserDB,
)
app.include_router(fastapi_users.router, prefix="/users", tags=["users"])
@fastapi_users.on_after_register()
def on_after_register(user: User, request: Request):
print(f"User {user.id} has registered.")
@fastapi_users.on_after_forgot_password()
def on_after_forgot_password(user: User, token: str, request: Request):
print(f"User {user.id} has forgot their password. Reset token: {token}")
```
### After
```py
def on_after_register(user: UserDB, request: Request):
print(f"User {user.id} has registered.")
def on_after_forgot_password(user: UserDB, token: str, request: Request):
print(f"User {user.id} has forgot their password. Reset token: {token}")
jwt_authentication = JWTAuthentication(secret=SECRET, lifetime_seconds=3600)
app = FastAPI()
fastapi_users = FastAPIUsers(
user_db, [jwt_authentication], User, UserCreate, UserUpdate, UserDB,
)
app.include_router(
fastapi_users.get_auth_router(jwt_authentication), prefix="/auth/jwt", tags=["auth"]
)
app.include_router(
fastapi_users.get_register_router(on_after_register), prefix="/auth", tags=["auth"]
)
app.include_router(
fastapi_users.get_reset_password_router(
SECRET, after_forgot_password=on_after_forgot_password
),
prefix="/auth",
tags=["auth"],
)
app.include_router(fastapi_users.get_users_router(), prefix="/users", tags=["users"])
```
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.

View File

@@ -1,9 +1,8 @@
import motor.motor_asyncio
from fastapi import FastAPI
from fastapi import FastAPI, Request
from fastapi_users import FastAPIUsers, models
from fastapi_users.authentication import JWTAuthentication
from fastapi_users.db import MongoDBUserDatabase
from starlette.requests import Request
DATABASE_URL = "mongodb://localhost:27017"
SECRET = "SECRET"
@@ -32,22 +31,32 @@ db = client["database_name"]
collection = db["users"]
user_db = MongoDBUserDatabase(UserDB, collection)
auth_backends = [
JWTAuthentication(secret=SECRET, lifetime_seconds=3600),
]
app = FastAPI()
fastapi_users = FastAPIUsers(
user_db, auth_backends, User, UserCreate, UserUpdate, UserDB, SECRET,
)
app.include_router(fastapi_users.router, prefix="/users", tags=["users"])
@fastapi_users.on_after_register()
def on_after_register(user: User, request: Request):
def on_after_register(user: UserDB, request: Request):
print(f"User {user.id} has registered.")
@fastapi_users.on_after_forgot_password()
def on_after_forgot_password(user: User, token: str, request: Request):
def on_after_forgot_password(user: UserDB, token: str, request: Request):
print(f"User {user.id} has forgot their password. Reset token: {token}")
jwt_authentication = JWTAuthentication(secret=SECRET, lifetime_seconds=3600)
app = FastAPI()
fastapi_users = FastAPIUsers(
user_db, [jwt_authentication], User, UserCreate, UserUpdate, UserDB,
)
app.include_router(
fastapi_users.get_auth_router(jwt_authentication), prefix="/auth/jwt", tags=["auth"]
)
app.include_router(
fastapi_users.get_register_router(on_after_register), prefix="/auth", tags=["auth"]
)
app.include_router(
fastapi_users.get_reset_password_router(
SECRET, after_forgot_password=on_after_forgot_password
),
prefix="/auth",
tags=["auth"],
)
app.include_router(fastapi_users.get_users_router(), prefix="/users", tags=["users"])

View File

@@ -1,11 +1,10 @@
import databases
import sqlalchemy
from fastapi import FastAPI
from fastapi import FastAPI, Request
from fastapi_users import FastAPIUsers, models
from fastapi_users.authentication import JWTAuthentication
from fastapi_users.db import SQLAlchemyBaseUserTable, SQLAlchemyUserDatabase
from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base
from starlette.requests import Request
DATABASE_URL = "sqlite:///./test.db"
SECRET = "SECRET"
@@ -44,27 +43,36 @@ users = UserTable.__table__
user_db = SQLAlchemyUserDatabase(UserDB, database, users)
auth_backends = [
JWTAuthentication(secret=SECRET, lifetime_seconds=3600),
]
app = FastAPI()
fastapi_users = FastAPIUsers(
user_db, auth_backends, User, UserCreate, UserUpdate, UserDB, SECRET,
)
app.include_router(fastapi_users.router, prefix="/users", tags=["users"])
@fastapi_users.on_after_register()
def on_after_register(user: User, request: Request):
def on_after_register(user: UserDB, request: Request):
print(f"User {user.id} has registered.")
@fastapi_users.on_after_forgot_password()
def on_after_forgot_password(user: User, token: str, request: Request):
def on_after_forgot_password(user: UserDB, token: str, request: Request):
print(f"User {user.id} has forgot their password. Reset token: {token}")
jwt_authentication = JWTAuthentication(secret=SECRET, lifetime_seconds=3600)
app = FastAPI()
fastapi_users = FastAPIUsers(
user_db, [jwt_authentication], User, UserCreate, UserUpdate, UserDB,
)
app.include_router(
fastapi_users.get_auth_router(jwt_authentication), prefix="/auth/jwt", tags=["auth"]
)
app.include_router(
fastapi_users.get_register_router(on_after_register), prefix="/auth", tags=["auth"]
)
app.include_router(
fastapi_users.get_reset_password_router(
SECRET, after_forgot_password=on_after_forgot_password
),
prefix="/auth",
tags=["auth"],
)
app.include_router(fastapi_users.get_users_router(), prefix="/users", tags=["users"])
@app.on_event("startup")
async def startup():
await database.connect()

View File

@@ -1,8 +1,7 @@
from fastapi import FastAPI
from fastapi import FastAPI, Request
from fastapi_users import FastAPIUsers, models
from fastapi_users.authentication import JWTAuthentication
from fastapi_users.db import TortoiseBaseUserModel, TortoiseUserDatabase
from starlette.requests import Request
from tortoise.contrib.starlette import register_tortoise
DATABASE_URL = "sqlite://./test.db"
@@ -33,21 +32,31 @@ user_db = TortoiseUserDatabase(UserDB, UserModel)
app = FastAPI()
register_tortoise(app, db_url=DATABASE_URL, modules={"models": ["test"]})
auth_backends = [
JWTAuthentication(secret=SECRET, lifetime_seconds=3600),
]
fastapi_users = FastAPIUsers(
user_db, auth_backends, User, UserCreate, UserUpdate, UserDB, SECRET,
)
app.include_router(fastapi_users.router, prefix="/users", tags=["users"])
@fastapi_users.on_after_register()
def on_after_register(user: User, request: Request):
def on_after_register(user: UserDB, request: Request):
print(f"User {user.id} has registered.")
@fastapi_users.on_after_forgot_password()
def on_after_forgot_password(user: User, token: str, request: Request):
def on_after_forgot_password(user: UserDB, token: str, request: Request):
print(f"User {user.id} has forgot their password. Reset token: {token}")
jwt_authentication = JWTAuthentication(secret=SECRET, lifetime_seconds=3600)
fastapi_users = FastAPIUsers(
user_db, [jwt_authentication], User, UserCreate, UserUpdate, UserDB,
)
app.include_router(
fastapi_users.get_auth_router(jwt_authentication), prefix="/auth/jwt", tags=["auth"]
)
app.include_router(
fastapi_users.get_register_router(on_after_register), prefix="/auth", tags=["auth"]
)
app.include_router(
fastapi_users.get_reset_password_router(
SECRET, after_forgot_password=on_after_forgot_password
),
prefix="/auth",
tags=["auth"],
)
app.include_router(fastapi_users.get_users_router(), prefix="/users", tags=["users"])

View File

@@ -1,10 +1,9 @@
import motor.motor_asyncio
from fastapi import FastAPI
from fastapi import FastAPI, Request
from fastapi_users import FastAPIUsers, models
from fastapi_users.authentication import JWTAuthentication
from fastapi_users.db import MongoDBUserDatabase
from httpx_oauth.clients.google import GoogleOAuth2
from starlette.requests import Request
DATABASE_URL = "mongodb://localhost:27017"
SECRET = "SECRET"
@@ -36,25 +35,37 @@ db = client["database_name"]
collection = db["users"]
user_db = MongoDBUserDatabase(UserDB, collection)
auth_backends = [
JWTAuthentication(secret=SECRET, lifetime_seconds=3600),
]
app = FastAPI()
fastapi_users = FastAPIUsers(
user_db, auth_backends, User, UserCreate, UserUpdate, UserDB, SECRET,
)
app.include_router(fastapi_users.router, prefix="/users", tags=["users"])
google_oauth_router = fastapi_users.get_oauth_router(google_oauth_client, SECRET)
app.include_router(google_oauth_router, prefix="/google-oauth", tags=["users"])
@fastapi_users.on_after_register()
def on_after_register(user: User, request: Request):
def on_after_register(user: UserDB, request: Request):
print(f"User {user.id} has registered.")
@fastapi_users.on_after_forgot_password()
def on_after_forgot_password(user: User, token: str, request: Request):
def on_after_forgot_password(user: UserDB, token: str, request: Request):
print(f"User {user.id} has forgot their password. Reset token: {token}")
jwt_authentication = JWTAuthentication(secret=SECRET, lifetime_seconds=3600)
app = FastAPI()
fastapi_users = FastAPIUsers(
user_db, [jwt_authentication], User, UserCreate, UserUpdate, UserDB,
)
app.include_router(
fastapi_users.get_auth_router(jwt_authentication), prefix="/auth/jwt", tags=["auth"]
)
app.include_router(
fastapi_users.get_register_router(on_after_register), prefix="/auth", tags=["auth"]
)
app.include_router(
fastapi_users.get_reset_password_router(
SECRET, after_forgot_password=on_after_forgot_password
),
prefix="/auth",
tags=["auth"],
)
app.include_router(fastapi_users.get_users_router(), prefix="/users", tags=["users"])
google_oauth_router = fastapi_users.get_oauth_router(
google_oauth_client, SECRET, after_register=on_after_register
)
app.include_router(google_oauth_router, prefix="/auth/google", tags=["auth"])

View File

@@ -1,6 +1,6 @@
import databases
import sqlalchemy
from fastapi import FastAPI
from fastapi import FastAPI, Request
from fastapi_users import FastAPIUsers, models
from fastapi_users.authentication import JWTAuthentication
from fastapi_users.db import (
@@ -10,7 +10,6 @@ from fastapi_users.db import (
)
from httpx_oauth.clients.google import GoogleOAuth2
from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base
from starlette.requests import Request
DATABASE_URL = "sqlite:///./test.db"
SECRET = "SECRET"
@@ -57,30 +56,41 @@ oauth_accounts = OAuthAccount.__table__
user_db = SQLAlchemyUserDatabase(UserDB, database, users, oauth_accounts)
auth_backends = [
JWTAuthentication(secret=SECRET, lifetime_seconds=3600),
]
app = FastAPI()
fastapi_users = FastAPIUsers(
user_db, auth_backends, User, UserCreate, UserUpdate, UserDB, SECRET,
)
app.include_router(fastapi_users.router, prefix="/users", tags=["users"])
google_oauth_router = fastapi_users.get_oauth_router(google_oauth_client, SECRET)
app.include_router(google_oauth_router, prefix="/google-oauth", tags=["users"])
@fastapi_users.on_after_register()
def on_after_register(user: User, request: Request):
def on_after_register(user: UserDB, request: Request):
print(f"User {user.id} has registered.")
@fastapi_users.on_after_forgot_password()
def on_after_forgot_password(user: User, token: str, request: Request):
def on_after_forgot_password(user: UserDB, token: str, request: Request):
print(f"User {user.id} has forgot their password. Reset token: {token}")
jwt_authentication = JWTAuthentication(secret=SECRET, lifetime_seconds=3600)
app = FastAPI()
fastapi_users = FastAPIUsers(
user_db, [jwt_authentication], User, UserCreate, UserUpdate, UserDB,
)
app.include_router(
fastapi_users.get_auth_router(jwt_authentication), prefix="/auth/jwt", tags=["auth"]
)
app.include_router(
fastapi_users.get_register_router(on_after_register), prefix="/auth", tags=["auth"]
)
app.include_router(
fastapi_users.get_reset_password_router(
SECRET, after_forgot_password=on_after_forgot_password
),
prefix="/auth",
tags=["auth"],
)
app.include_router(fastapi_users.get_users_router(), prefix="/users", tags=["users"])
google_oauth_router = fastapi_users.get_oauth_router(
google_oauth_client, SECRET, after_register=on_after_register
)
app.include_router(google_oauth_router, prefix="/auth/google", tags=["auth"])
@app.on_event("startup")
async def startup():
await database.connect()

View File

@@ -1,4 +1,4 @@
from fastapi import FastAPI
from fastapi import FastAPI, Request
from fastapi_users import FastAPIUsers, models
from fastapi_users.authentication import JWTAuthentication
from fastapi_users.db import (
@@ -7,7 +7,6 @@ from fastapi_users.db import (
TortoiseUserDatabase,
)
from httpx_oauth.clients.google import GoogleOAuth2
from starlette.requests import Request
from tortoise import fields
from tortoise.contrib.starlette import register_tortoise
@@ -46,24 +45,36 @@ user_db = TortoiseUserDatabase(UserDB, UserModel, OAuthAccountModel)
app = FastAPI()
register_tortoise(app, db_url=DATABASE_URL, modules={"models": ["test"]})
auth_backends = [
JWTAuthentication(secret=SECRET, lifetime_seconds=3600),
]
fastapi_users = FastAPIUsers(
user_db, auth_backends, User, UserCreate, UserUpdate, UserDB, SECRET,
)
app.include_router(fastapi_users.router, prefix="/users", tags=["users"])
google_oauth_router = fastapi_users.get_oauth_router(google_oauth_client, SECRET)
app.include_router(google_oauth_router, prefix="/google-oauth", tags=["users"])
@fastapi_users.on_after_register()
def on_after_register(user: User, request: Request):
def on_after_register(user: UserDB, request: Request):
print(f"User {user.id} has registered.")
@fastapi_users.on_after_forgot_password()
def on_after_forgot_password(user: User, token: str, request: Request):
def on_after_forgot_password(user: UserDB, token: str, request: Request):
print(f"User {user.id} has forgot their password. Reset token: {token}")
jwt_authentication = JWTAuthentication(secret=SECRET, lifetime_seconds=3600)
fastapi_users = FastAPIUsers(
user_db, [jwt_authentication], User, UserCreate, UserUpdate, UserDB,
)
app.include_router(
fastapi_users.get_auth_router(jwt_authentication), prefix="/auth/jwt", tags=["auth"]
)
app.include_router(
fastapi_users.get_register_router(on_after_register), prefix="/auth", tags=["auth"]
)
app.include_router(
fastapi_users.get_reset_password_router(
SECRET, after_forgot_password=on_after_forgot_password
),
prefix="/auth",
tags=["auth"],
)
app.include_router(fastapi_users.get_users_router(), prefix="/users", tags=["users"])
google_oauth_router = fastapi_users.get_oauth_router(
google_oauth_client, SECRET, after_register=on_after_register
)
app.include_router(google_oauth_router, prefix="/auth/google", tags=["auth"])

View File

@@ -45,4 +45,4 @@ def protected_route():
return 'Hello, some user.'
```
You can see more about it [in FastAPI docs](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/).
You can read more about this [in FastAPI docs](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/).

View File

@@ -14,12 +14,12 @@ First step, of course, is to register as a user.
-H "Content-Type: application/json" \
-X POST \
-d "{\"email\": \"king.arthur@camelot.bt\",\"password\": \"guinevere\"}" \
http://localhost:8000/users/register
http://localhost:8000/auth/register
```
=== "axios"
```ts
axios.post('http://localhost:8000/users/register', {
axios.post('http://localhost:8000/auth/register', {
email: 'king.arthur@camelot.bt',
password: 'guinevere',
})
@@ -51,7 +51,7 @@ You'll get a JSON response looking like this:
Now, you can login as this new user.
Each [authentication backend](../configuration/authentication/index.md) will produce a single route. For example, the [JWT backend](../configuration/authentication/jwt.md) will produce the `/users/login/jwt` route. Each backend will have a different response.
You can generate a [login route](../configuration/routers/auth.md) for each [authentication backend](../configuration/authentication/index.md). Each backend will have a different response.
### JWT backend
@@ -64,7 +64,7 @@ Each [authentication backend](../configuration/authentication/index.md) will pro
-X POST \
-F "username=king.arthur@camelot.bt" \
-F "password=guinevere" \
http://localhost:8000/users/login/jwt
http://localhost:8000/auth/jwt/login
```
=== "axios"
@@ -73,7 +73,7 @@ Each [authentication backend](../configuration/authentication/index.md) will pro
formData.set('username', 'king.arthur@camelot.bt');
formData.set('password', 'guinevere');
axios.post(
'http://localhost:8000/users/login/jwt',
'http://localhost:8000/auth/jwt/login',
formData,
{
headers: {
@@ -112,7 +112,7 @@ You can use this token to make authenticated requests as the user `king.arthur@c
-X POST \
-F "username=king.arthur@camelot.bt" \
-F "password=guinevere" \
http://localhost:8000/users/login/cookie
http://localhost:8000/auth/cookie/login
```
=== "axios"
@@ -121,7 +121,7 @@ You can use this token to make authenticated requests as the user `king.arthur@c
formData.set('username', 'king.arthur@camelot.bt');
formData.set('password', 'guinevere');
axios.post(
'http://localhost:8000/users/login/cookie',
'http://localhost:8000/auth/cookie/login',
formData,
{
headers: {
@@ -380,12 +380,12 @@ We can also end the session. Note that it doesn't apply to every [authentication
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-X POST \
http://localhost:8000/users/logout/cookie
http://localhost:8000/auth/cookie/logout
```
=== "axios"
```ts
axios.post('http://localhost:8000/users/logout/cookie',
axios.post('http://localhost:8000/auth/cookie/logout',
null,
{
headers: {

View File

@@ -2,11 +2,49 @@
You'll find here the routes exposed by **FastAPI Users**. Note that you can also review them through the [interactive API docs](https://fastapi.tiangolo.com/tutorial/first-steps/#interactive-api-docs).
## Unauthenticated
## Auth router
Each [authentication backend](../configuration/authentication/index.md) you [generate a router for](../configuration/routers/auth.md) will produce the following routes. Take care about the prefix you gave it, especially if you have several backends.
### `POST /login`
Login a user against the method named `name`. Check the corresponding [authentication method](../configuration/authentication/index.md) to view the success response.
!!! abstract "Payload (`application/x-www-form-urlencoded`)"
```
username=king.arthur@camelot.bt&password=guinevere
```
!!! fail "`422 Validation Error`"
!!! fail "`400 Bad Request`"
Bad credentials or the user is inactive.
```json
{
"detail": "LOGIN_BAD_CREDENTIALS"
}
```
### `POST /logout`
Logout the authenticated user against the method named `name`. Check the corresponding [authentication method](../configuration/authentication/index.md) to view the success response.
!!! fail "`401 Unauthorized`"
Missing token or inactive user.
!!! success "`200 OK`"
The logout process was successful.
!!! tip
Some backend (like JWT) won't produce this route.
## Register router
### `POST /register`
Register a new user. Will call the `on_after_register` [event handlers](../configuration/router.md#event-handlers) on successful registration.
Register a new user. Will call the `after_register` [handler](../configuration/routers/register.md#after-register) on successful registration.
!!! abstract "Payload"
```json
@@ -37,42 +75,11 @@ Register a new user. Will call the `on_after_register` [event handlers](../confi
}
```
### `POST /login/{name}`
Login a user against the method named `name`. Check the corresponding [authentication method](../configuration/authentication/index.md) to view the success response.
!!! abstract "Payload (`application/x-www-form-urlencoded`)"
```
username=king.arthur@camelot.bt&password=guinevere
```
!!! fail "`422 Validation Error`"
!!! fail "`400 Bad Request`"
Bad credentials or the user is inactive.
```json
{
"detail": "LOGIN_BAD_CREDENTIALS"
}
```
### `POST /logout/{name}`
Logout the authenticated user against the method named `name`. Check the corresponding [authentication method](../configuration/authentication/index.md) to view the success response.
!!! fail "`401 Unauthorized`"
Missing token or inactive user.
!!! success "`200 OK`"
The logout process was successful.
!!! success "`202 Accepted`"
The logout process is not applicable for this authentication backend (e.g. JWT).
## Reset password router
### `POST /forgot-password`
Request a reset password procedure. Will generate a temporary token and call the `on_after_forgot_password` [event handlers](../configuration/router.md#event-handlers) if the user exists.
Request a reset password procedure. Will generate a temporary token and call the `after_forgot_password` [handlers](../configuration/routers/reset.md#after-forgot-password) if the user exists.
To prevent malicious users from guessing existing users in your databse, the route will always return a `202 Accepted` response, even if the user requested does not exist.
@@ -110,11 +117,11 @@ Reset a password. Requires the token generated by the `/forgot-password` route.
}
```
### OAuth routes
## OAuth router
Each OAuth router you define will expose the two following routes.
#### `GET /authorize`
### `GET /authorize`
Return the authorization URL for the OAuth service where you should redirect your user.
@@ -134,7 +141,7 @@ Return the authorization URL for the OAuth service where you should redirect you
!!! fail "`400 Bad Request`"
Unknown authentication backend.
#### `GET /callback`
### `GET /callback`
Handle the OAuth callback.
@@ -155,7 +162,7 @@ Depending on the situation, several things can happen:
* A new user is created and linked to the OAuth account.
* The user is authenticated following the chosen [authentication method](../configuration/authentication/index.md).
## Authenticated
## Users router
### `GET /me`
@@ -199,8 +206,6 @@ Update the current authenticated active user.
!!! fail "`401 Unauthorized`"
Missing token or inactive user.
## Superuser
### `GET /{user_id}`
Return the user with id `user_id`.