diff --git a/docs/configuration/oauth.md b/docs/configuration/oauth.md index 5adf4504..118e854e 100644 --- a/docs/configuration/oauth.md +++ b/docs/configuration/oauth.md @@ -65,7 +65,7 @@ The advantage of MongoDB is that you can easily embed sub-objects in a single do 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 a router +### Generate routers Once you have a `FastAPIUsers` instance, you can make it generate a single OAuth router for a given client **and** authentication backend. @@ -77,6 +77,50 @@ app.include_router( ) ``` +!!! 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. + +#### Existing account association + +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: + +```py +app.include_router( + fastapi_users.get_oauth_router( + google_oauth_client, + auth_backend, + "SECRET", + associate_by_email=True, + ), + prefix="/auth/google", + tags=["auth"], +) +``` + +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 😞 + +#### Association router for authenticated users + +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. + +```py +app.include_router( + fastapi_users.get_oauth_associate_router(google_oauth_client, UserRead, "SECRET"), + prefix="/auth/associate/google", + tags=["auth"], +) +``` + +Notice that, just like for the [Users router](./routers/users.md), you have to pass the `UserRead` Pydantic schema. + ### Full example !!! warning diff --git a/docs/configuration/overview.md b/docs/configuration/overview.md index a14392e6..3961223b 100644 --- a/docs/configuration/overview.md +++ b/docs/configuration/overview.md @@ -23,6 +23,7 @@ flowchart TB subgraph ROUTERS[Routers] AUTH[[get_auth_router]] OAUTH[[get_oauth_router]] + OAUTH_ASSOCIATE[[get_oauth_associate_router]] REGISTER[[get_register_router]] VERIFY[[get_verify_router]] RESET[[get_reset_password_router]] diff --git a/docs/usage/routes.md b/docs/usage/routes.md index b66a0ae4..9dcc98c7 100644 --- a/docs/usage/routes.md +++ b/docs/usage/routes.md @@ -214,9 +214,6 @@ Return the authorization URL for the OAuth service where you should redirect you } ``` -!!! fail "`422 Validation Error`" - Invalid parameters - e.g. unknown authentication backend. - ### `GET /callback` Handle the OAuth callback. @@ -232,8 +229,8 @@ Depending on the situation, several things can happen: * OAuth account is updated in database with fresh access token. * The user is authenticated following the chosen [authentication method](../configuration/authentication/index.md). * The OAuth account doesn't exist in database but a user with the same email address exists: - * OAuth account is linked to the user. - * The user is authenticated following the chosen [authentication method](../configuration/authentication/index.md). + * By default, an HTTP 400 error is raised. + * If [the `associate_by_email` flag is set to `True`](../configuration/oauth.md#existing-account-association) on the router declaration, OAuth account is linked to the user. The user is authenticated following the chosen [authentication method](../configuration/authentication/index.md). * 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](../configuration/authentication/index.md). @@ -241,6 +238,15 @@ Depending on the situation, several things can happen: !!! fail "`400 Bad Request`" Invalid token. +!!! fail "`400 Bad Request`" + Another user with the same e-mail address already exists. + + ```json + { + "detail": "OAUTH_USER_ALREADY_EXISTS" + } + ``` + !!! fail "`400 Bad Request`" User is inactive. @@ -250,6 +256,63 @@ Depending on the situation, several things can happen: } ``` +## OAuth association router + +Each OAuth association router you define will expose the two following routes. + +### `GET /authorize` + +Return the authorization URL for the OAuth service where you should redirect your user. + +!!! abstract "Query parameters" + * `scopes`: Optional list of scopes to ask for. Expected format: `scopes=a&scopes=b`. + +!!! fail "`401 Unauthorized`" + Missing token or inactive user. + +!!! success "`200 OK`" + ```json + { + "authorization_url": "https://www.tintagel.bt/oauth/authorize?client_id=CLIENT_ID&scopes=a+b&redirect_uri=https://www.camelot.bt/oauth/callback" + } + ``` + +### `GET /callback` + +Handle the OAuth callback and add the OAuth account to the current authenticated active user. + +!!! abstract "Query parameters" + * `code`: OAuth callback code. + * `state`: State token. + * `error`: OAuth error. + +!!! fail "`401 Unauthorized`" + Missing token or inactive user. + +!!! fail "`400 Bad Request`" + Invalid token. + +!!! success "`200 OK`" + ```json + { + "id": "57cbb51a-ab71-4009-8802-3f54b4f2e23", + "email": "king.arthur@tintagel.bt", + "is_active": true, + "is_superuser": false, + "oauth_accounts": [ + { + "id": "6c98caf5-9bc5-4c4f-8a45-a0ae0c40cd77", + "oauth_name": "TINTAGEL", + "access_token": "ACCESS_TOKEN", + "expires_at": "1641040620", + "account_id": "king_arthur_tintagel", + "account_email": "king.arthur@tintagel.bt" + } + ] + } + ``` + + ## Users router ### `GET /me`