diff --git a/README.md b/README.md index 226439c..0aba9ee 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ Also, [we're hiring!](https://intility.no/en/career/) ## 📚 Resources The [documentation](https://intility.github.io/fastapi-azure-auth/) contains a full tutorial on how to configure Azure AD -and FastAPI for both single- and multi-tenant applications. It includes examples on how to lock down +and FastAPI for single- and multi-tenant applications as well as B2C apps. It includes examples on how to lock down your APIs to certain scopes, tenants, roles etc. For first time users it's strongly advised to set up your application exactly how it's described there, and then alter it to your needs later. @@ -99,8 +99,7 @@ app = FastAPI( Ensure you have CORS enabled for your local environment, such as `http://localhost:8000`. #### 4. Configure FastAPI-Azure-Auth -Configure either your [`SingleTenantAzureAuthorizationCodeBearer`](https://intility.github.io/fastapi-azure-auth/settings/single_tenant) -or [`MultiTenantAzureAuthorizationCodeBearer`](https://intility.github.io/fastapi-azure-auth/settings/multi_tenant). +Configure either [`SingleTenantAzureAuthorizationCodeBearer`](https://intility.github.io/fastapi-azure-auth/settings/single_tenant), [`MultiTenantAzureAuthorizationCodeBearer`](https://intility.github.io/fastapi-azure-auth/settings/multi_tenant) or [`B2CMultiTenantAuthorizationCodeBearer`](https://intility.github.io/fastapi-azure-auth/settings/b2c) ```python diff --git a/docs/docs/b2c/_category_.json b/docs/docs/b2c/_category_.json new file mode 100644 index 0000000..321a9ce --- /dev/null +++ b/docs/docs/b2c/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "B2C setup", + "position": 4, + "collapsible": true +} diff --git a/docs/docs/b2c/azure_setup.mdx b/docs/docs/b2c/azure_setup.mdx new file mode 100644 index 0000000..a64d265 --- /dev/null +++ b/docs/docs/b2c/azure_setup.mdx @@ -0,0 +1,191 @@ +--- +title: Azure configuration +sidebar_position: 1 +--- + +We'll need to create two application registrations for Azure AD B2C authentication to cover both direct API +use and usage from the OpenAPI (swagger) documentation. + +:::info +This guide assumes that an Azure B2C tenant was already created and linked to an Azure subscription. +::: + + +## Backend API + +### Step 1 - Create app registration +Head over to +[Azure -> Azure AD B2C -> App registrations](https://portal.azure.com/#view/Microsoft_AAD_B2CAdmin/TenantManagementMenuBlade/~/registeredApps), +and create a new registration. + +Select a fitting name for your project; Azure will present the name to the user during consent. + +* `Supported account types`: `Accounts in any identity provider or organizational directory (for authenticating users with user flows)` +* `Redirect URI`: Choose `Web` and `http://localhost:8000/signin-oidc` as a value +* `Grant admin consent to openid and offline_access permissions`: `Yes` + +Press **Register** + +![1_application_registration](../../static/img/b2c/1_application_registration.png) + +### Step 2 - Verify token version is `v2` + +First we'll verify that the token version is version 2. In the left menu bar, click `Manifest` and find the line +that says `accessTokenAcceptedVersion`. Verify that the value is `2`. + +Press **Save** + +(A change can take some time to happen, which is why we do this first.) + +![2_manifest](../../static/img/b2c/2_manifest.png) + + +### Step 3 - Note down your tenant name and application ID + +Go back to the App Registration `Overview`, found in the left menu. + +![3_overview](../../static/img/b2c/3_overview.png) + +Copy the `Application (Client) ID`, we'll need it for later. I like to use `.env` files to +store variables like these: + +```bash title=".env" {2} +TENANT_NAME= +APP_CLIENT_ID= +OPENAPI_CLIENT_ID= +AUTH_POLICY_NAME= +``` + +Also, in the Azure AD B2C overview get the tenant name from the domain name (without the `.onmicrosoft.com` part) +and add it to the `.env` file as well: + +```bash title=".env" {1} +TENANT_NAME= +APP_CLIENT_ID= +OPENAPI_CLIENT_ID= +AUTH_POLICY_NAME= +``` + + + + +### Step 4 - Add an application scope + +1. Go to **Expose an API** in the left menu bar under your app registration. +2. Press **+ Add a scope** +3. You'll be prompted to set an Application ID URI, leave the suggested one and press **Save and continue** +![4_add_scope](../../static/img/b2c/4_add_scope.png) + +4. You'll be prompted to set a scope name, display name and description. Set the scope name to `user_impersonation`, +display name to `Access API as user` and description to `Allows the app to access the API as the user.` +5. Make sure the State is `Enabled` +6. Press **Add scope** + + +![5_add_scope_props](../../static/img/b2c/5_add_scope_props.png) + + +## OpenAPI Documentation +Our OpenAPI documentation will use the `Authorization Code Grant Flow, with Proof Key for Code Exchange` flow. +It's a flow that enables a user of a Single-Page Application to safely log in, consent to permissions and fetch an `access_token` +in the `JWT` format. When the user clicks `Try out` on the APIs, the `access_token` is attached to the header as a `Bearer ` token. +This is the token the backend will validate. + +So, let's set it up! + +### Step 1 - Create app registration +Just like in the previous chapter, we have to create an application registration for our OpenAPI. + +Head over to +[Azure -> Azure AD B2C -> App registrations](https://portal.azure.com/#view/Microsoft_AAD_B2CAdmin/TenantManagementMenuBlade/~/registeredApps), +and create a new registration. + +Use the same name, but with `- OpenAPI` appended to it. + +* `Supported account types`: `Accounts in any identity provider or organizational directory (for authenticating users with user flows)` +* `Redirect URI`: Choose `Single-Page Application (SPA)` and `http://localhost:8000/oauth2-redirect` as a value +* `Grant admin consent to openid and offline_access permissions`: `Yes` + +Press **Register** + +![6_application_registration_openapi](../../static/img/b2c/6_application_registration_openapi.png) + + +### Step 2 - Change token version to `v2` + +Like last time, we'll verify the token version is set to version 2. In the left menu bar, click `Manifest` and find the line +that says `accessTokenAcceptedVersion`. Verify the value is `2`. + +Press **Save** + +![3_manifest](../../static/img/b2c/2_manifest.png) + + +### Step 3 - Note down your application IDs +Go back to the `Overview`, found in the left menu. + +Copy the `Application (Client) ID` and save it as your `OPENAPI_CLIENT_ID`: + +```bash title=".env" {3} +TENANT_NAME= +APP_CLIENT_ID= +OPENAPI_CLIENT_ID= +AUTH_POLICY_NAME= +```` + +![7_overview_openapi](../../static/img/b2c/7_overview_openapi.png) + + +### Step 4 - Allow OpenAPI to talk to the backend + +To allow OpenAPI to talk to the backend API, you must add API permissions to the OpenAPI app registration. +In the left menu, go to **API Permissions** and **Add a permission**. + +![8_api_permissions](../../static/img/b2c/8_api_permissions.png) + +Select the `user_impersonation` scope, and press **Add a permission**. + +Your view should now look something like this: + +![9_api_permissions_finish](../../static/img/b2c/9_api_permissions_finish.png) + +That's it! Next step is to configure the FastAPI application. + + +## User flows + +### Step 1 - Create a user flow + +Head over to +[Azure -> Azure AD B2C -> Users flows](https://portal.azure.com/#view/Microsoft_AAD_B2CAdmin/TenantManagementMenuBlade/~/userJourneys), +and create a new user flow. + +Select a user flow type of `Sign up and sign in` with the Version `Recommended`, then press **Create**. + +![10_create_user_flow](../../static/img/b2c/10_add_user_flow.png) + +You are prompted to fill out details of the new user flow. + +Give it a name of `sign_up_sign_in` (note that `B2C_1_` is already prefixed), and choose `Email signup` as the identity provider. + +![11_add_user_flow_props_name_provider](../../static/img/b2c/11_add_user_flow_props_name_provider.png) + + +Keep all defaults for now and choose user attributes and token claims as required, and press **Create** + +![12_add_user_flow_props_attributes_claims](../../static/img/b2c/12_add_user_flow_props_attributes_claims.png) + + +### Step 2 - Note down your User flow name + +Copy the User Flow name just created (including the `B2C_1_` prefix, e.g. `B2C_1_sign_up_sign_in`) +and save it in the .env file: + +```bash title=".env" {4} +TENANT_NAME= +APP_CLIENT_ID= +OPENAPI_CLIENT_ID= +AUTH_POLICY_NAME=B2C_1_sign_up_sign_in +```` + +That's it! Next step is to configure the FastAPI application. diff --git a/docs/docs/b2c/fastapi_configuration.mdx b/docs/docs/b2c/fastapi_configuration.mdx new file mode 100644 index 0000000..9bcb10b --- /dev/null +++ b/docs/docs/b2c/fastapi_configuration.mdx @@ -0,0 +1,472 @@ +--- +title: FastAPI configuration +sidebar_position: 2 +--- +import GitHubButton from 'react-github-btn'; + +We'll do the **simplest setup possible** in these docs, through a one-file `main.py`. +However, it's highly recommended that you read the chapters about bigger applications +[here](https://fastapi.tiangolo.com/tutorial/bigger-applications/), and invest in a good project structure. + +We assume you've done the FastAPI tutorial and have dependencies installed, such as `FastAPI` and `Gunicorn`. + +For a more "real life" project example, look at the +[demo_project](https://github.com/Intility/fastapi-azure-auth/tree/main/demo_project) on GitHub. + +## Getting started +First, either create your `.env` file and fill out your variables or insert them directly in your settings later. + +```bash title=".env" +TENANT_NAME= +APP_CLIENT_ID= +OPENAPI_CLIENT_ID= +AUTH_POLICY_NAME= +``` + +Create your `main.py` file: + +```python title="main.py" +from fastapi import FastAPI +import uvicorn + +app = FastAPI() + + +@app.get("/") +async def root(): + return {"message": "Hello World"} + +if __name__ == '__main__': + uvicorn.run('main:app', reload=True) +``` +Run your application and ensure that everything works on [http://localhost:8000/docs](http://localhost:8000/docs) + +:::info +You need to run the application on the configured port in Azure AD B2C for the next steps to work! If you are unsure, +revisit the previous chapter or review the Azure AD B2C configuration under `App Registrations` -> `Authentication`. +::: + +## Add your settings + +First, add your settings to the application. We'll need these later. The way I've set it up will look for a `.env`-file +to populate your settings, but you can also just set a `default` value directly. + +```python {1,5,8-20} title="main.py" +from typing import Union + +import uvicorn +from fastapi import FastAPI +from pydantic import AnyHttpUrl, BaseSettings, Field + + +class Settings(BaseSettings): + BACKEND_CORS_ORIGINS: list[Union[str, AnyHttpUrl]] = ['http://localhost:8000'] + TENANT_NAME: str = Field(default='', env='TENANT_NAME') + APP_CLIENT_ID: str = Field(default='', env='APP_CLIENT_ID') + OPENAPI_CLIENT_ID: str = Field(default='', env='OPENAPI_CLIENT_ID') + AUTH_POLICY_NAME: str = Field(default='', env='AUTH_POLICY_NAME') + + class Config: + env_file = '.env' + env_file_encoding = 'utf-8' + case_sensitive = True + +settings = Settings() + +app = FastAPI() + +@app.get("/") +async def root(): + return {"message": "Hello World"} + + +if __name__ == '__main__': + uvicorn.run('main:app', reload=True) +``` + +## Configure `CORS` + +Now, let's configure our `CORS`. Without `CORS` your OpenAPI docs won't work as expected: + +```python {5,25-32} title="main.py" +from typing import Union + +import uvicorn +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from pydantic import AnyHttpUrl, BaseSettings, Field + + +class Settings(BaseSettings): + BACKEND_CORS_ORIGINS: list[Union[str, AnyHttpUrl]] = ['http://localhost:8000'] + TENANT_NAME: str = Field(default='', env='TENANT_NAME') + APP_CLIENT_ID: str = Field(default='', env='APP_CLIENT_ID') + OPENAPI_CLIENT_ID: str = Field(default='', env='OPENAPI_CLIENT_ID') + AUTH_POLICY_NAME: str = Field(default='', env='AUTH_POLICY_NAME') + + class Config: + env_file = '.env' + env_file_encoding = 'utf-8' + case_sensitive = True + +settings = Settings() + +app = FastAPI() + +if settings.BACKEND_CORS_ORIGINS: + app.add_middleware( + CORSMiddleware, + allow_origins=[str(origin) for origin in settings.BACKEND_CORS_ORIGINS], + allow_credentials=True, + allow_methods=['*'], + allow_headers=['*'], + ) + + +@app.get("/") +async def root(): + return {"message": "Hello World"} + + +if __name__ == '__main__': + uvicorn.run('main:app', reload=True) +``` + +## Configure OpenAPI Documentation +In order for our OpenAPI documentation to work, we have to configure a few settings directly in the `FastAPI` application. + +```python {23-29} title="main.py" +from typing import Union + +import uvicorn +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from pydantic import AnyHttpUrl, BaseSettings, Field + + +class Settings(BaseSettings): + BACKEND_CORS_ORIGINS: list[Union[str, AnyHttpUrl]] = ['http://localhost:8000'] + TENANT_NAME: str = Field(default='', env='TENANT_NAME') + APP_CLIENT_ID: str = Field(default='', env='APP_CLIENT_ID') + OPENAPI_CLIENT_ID: str = Field(default='', env='OPENAPI_CLIENT_ID') + AUTH_POLICY_NAME: str = Field(default='', env='AUTH_POLICY_NAME') + + class Config: + env_file = '.env' + env_file_encoding = 'utf-8' + case_sensitive = True + +settings = Settings() + +app = FastAPI( + swagger_ui_oauth2_redirect_url='/oauth2-redirect', + swagger_ui_init_oauth={ + 'usePkceWithAuthorizationCodeGrant': True, + 'clientId': settings.OPENAPI_CLIENT_ID, + }, +) + +if settings.BACKEND_CORS_ORIGINS: + app.add_middleware( + CORSMiddleware, + allow_origins=[str(origin) for origin in settings.BACKEND_CORS_ORIGINS], + allow_credentials=True, + allow_methods=['*'], + allow_headers=['*'], + ) + + +@app.get("/") +async def root(): + return {"message": "Hello World"} + + +if __name__ == '__main__': + uvicorn.run('main:app', reload=True) +``` + +The `swagger_ui_oauth2_redirect_url` setting for redirect should be as configured in Azure AD. +The `swagger_ui_init_oauth` are standard mapped OpenAPI properties. You can find documentation about them [here](https://swagger.io/docs/open-source-tools/swagger-ui/usage/oauth2/) + +We've used two flags: `usePkceWithAuthorizationCodeGrant`, which is the authentication flow. +`clientId` is our application Client ID, which will autofill a field for the end users later. + +## Implementing FastAPI-Azure-Auth + +Now, the fun part begins! 🚀 + +Import the `B2CMultiTenantAuthorizationCodeBearer` from `fastapi_azure_auth` and configure it: + + +```python {7,42-51} title="main.py" +from typing import Union + +import uvicorn +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from pydantic import AnyHttpUrl, BaseSettings, Field +from fastapi_azure_auth import B2CMultiTenantAuthorizationCodeBearer + + + +class Settings(BaseSettings): + BACKEND_CORS_ORIGINS: list[Union[str, AnyHttpUrl]] = ['http://localhost:8000'] + TENANT_NAME: str = Field(default='', env='TENANT_NAME') + APP_CLIENT_ID: str = Field(default='', env='APP_CLIENT_ID') + OPENAPI_CLIENT_ID: str = Field(default='', env='OPENAPI_CLIENT_ID') + AUTH_POLICY_NAME: str = Field(default='', env='AUTH_POLICY_NAME') + + class Config: + env_file = '.env' + env_file_encoding = 'utf-8' + case_sensitive = True + +settings = Settings() + +app = FastAPI( + swagger_ui_oauth2_redirect_url='/oauth2-redirect', + swagger_ui_init_oauth={ + 'usePkceWithAuthorizationCodeGrant': True, + 'clientId': settings.OPENAPI_CLIENT_ID, + }, +) + +if settings.BACKEND_CORS_ORIGINS: + app.add_middleware( + CORSMiddleware, + allow_origins=[str(origin) for origin in settings.BACKEND_CORS_ORIGINS], + allow_credentials=True, + allow_methods=['*'], + allow_headers=['*'], + ) + +azure_scheme = B2CMultiTenantAuthorizationCodeBearer( + app_client_id=settings.APP_CLIENT_ID, + openid_config_url=f'https://{settings.TENANT_NAME}.b2clogin.com/{settings.TENANT_NAME}.onmicrosoft.com/{settings.AUTH_POLICY_NAME}/v2.0/.well-known/openid-configuration', + openapi_authorization_url=f'https://{settings.TENANT_NAME}.b2clogin.com/{settings.TENANT_NAME}.onmicrosoft.com/{settings.AUTH_POLICY_NAME}/oauth2/v2.0/authorize', + openapi_token_url=f'https://{settings.TENANT_NAME}.b2clogin.com/{settings.TENANT_NAME}.onmicrosoft.com/{settings.AUTH_POLICY_NAME}/oauth2/v2.0/token', + scopes={ + f'https://{settings.TENANT_NAME}.onmicrosoft.com/{settings.APP_CLIENT_ID}/user_impersonation': 'user_impersonation', + }, + validate_iss=False, +) + + +@app.get("/") +async def root(): + return {"message": "Hello World"} + + +if __name__ == '__main__': + uvicorn.run('main:app', reload=True) +``` + +We pass the `app_client_id=` to be our Backend application ID. +The `openid_config_url` is composed of our Tenant Name and the Policy name given as a user flow. Lastly our +scope(s) are composed of the Tenant name as well as the Backend application ID. We'll get back to the scopes later. + +## Add loading of OpenID Configuration on startup + +By adding `on_event('startup')` we're able to load the OpenID configuration immediately, instead of doing it when +the first user authenticates. This isn't required, but makes things a bit quicker. When 24 hours has passed, the +configuration will be considered out of date, and update when a user does a request. You can use +[background tasks](https://fastapi.tiangolo.com/tutorial/background-tasks/) to refresh it before that happens if you'd like. + +```python {54-59} title="main.py" +from typing import Union + +import uvicorn +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from pydantic import AnyHttpUrl, BaseSettings, Field +from fastapi_azure_auth import B2CMultiTenantAuthorizationCodeBearer + + + +class Settings(BaseSettings): + BACKEND_CORS_ORIGINS: list[Union[str, AnyHttpUrl]] = ['http://localhost:8000'] + TENANT_NAME: str = Field(default='', env='TENANT_NAME') + APP_CLIENT_ID: str = Field(default='', env='APP_CLIENT_ID') + OPENAPI_CLIENT_ID: str = Field(default='', env='OPENAPI_CLIENT_ID') + AUTH_POLICY_NAME: str = Field(default='', env='AUTH_POLICY_NAME') + + class Config: + env_file = '.env' + env_file_encoding = 'utf-8' + case_sensitive = True + +settings = Settings() + +app = FastAPI( + swagger_ui_oauth2_redirect_url='/oauth2-redirect', + swagger_ui_init_oauth={ + 'usePkceWithAuthorizationCodeGrant': True, + 'clientId': settings.OPENAPI_CLIENT_ID, + }, +) + +if settings.BACKEND_CORS_ORIGINS: + app.add_middleware( + CORSMiddleware, + allow_origins=[str(origin) for origin in settings.BACKEND_CORS_ORIGINS], + allow_credentials=True, + allow_methods=['*'], + allow_headers=['*'], + ) + +azure_scheme = B2CMultiTenantAuthorizationCodeBearer( + app_client_id=settings.APP_CLIENT_ID, + openid_config_url=f'https://{settings.TENANT_NAME}.b2clogin.com/{settings.TENANT_NAME}.onmicrosoft.com/{settings.AUTH_POLICY_NAME}/v2.0/.well-known/openid-configuration', + openapi_authorization_url=f'https://{settings.TENANT_NAME}.b2clogin.com/{settings.TENANT_NAME}.onmicrosoft.com/{settings.AUTH_POLICY_NAME}/oauth2/v2.0/authorize', + openapi_token_url=f'https://{settings.TENANT_NAME}.b2clogin.com/{settings.TENANT_NAME}.onmicrosoft.com/{settings.AUTH_POLICY_NAME}/oauth2/v2.0/token', + scopes={ + f'https://{settings.TENANT_NAME}.onmicrosoft.com/{settings.APP_CLIENT_ID}/user_impersonation': 'user_impersonation', + }, + validate_iss=False, +) + + +@app.on_event('startup') +async def load_config() -> None: + """ + Load OpenID config on startup. + """ + await azure_scheme.openid_config.load_config() + + +@app.get("/") +async def root(): + return {"message": "Hello World"} + + +if __name__ == '__main__': + uvicorn.run('main:app', reload=True) +``` + +## Adding authentication to our view +There's two ways of adding dependencies in FastAPI. You can use `Depends()` or `Security()`. `Security()` has an extra +property called `scopes`. `FastAPI-Azure-Auth` support both, but if you use `Security()` you can also lock down your API +views based on the scope. + +Let's do that: + +```python {4,61} title="main.py" +from typing import Union + +import uvicorn +from fastapi import FastAPI, Security +from fastapi.middleware.cors import CORSMiddleware +from pydantic import AnyHttpUrl, BaseSettings, Field +from fastapi_azure_auth import B2CMultiTenantAuthorizationCodeBearer + + +class Settings(BaseSettings): + BACKEND_CORS_ORIGINS: list[Union[str, AnyHttpUrl]] = ['http://localhost:8000'] + TENANT_NAME: str = Field(default='', env='TENANT_NAME') + APP_CLIENT_ID: str = Field(default='', env='APP_CLIENT_ID') + OPENAPI_CLIENT_ID: str = Field(default='', env='OPENAPI_CLIENT_ID') + AUTH_POLICY_NAME: str = Field(default='', env='AUTH_POLICY_NAME') + + class Config: + env_file = '.env' + env_file_encoding = 'utf-8' + case_sensitive = True + +settings = Settings() + +app = FastAPI( + swagger_ui_oauth2_redirect_url='/oauth2-redirect', + swagger_ui_init_oauth={ + 'usePkceWithAuthorizationCodeGrant': True, + 'clientId': settings.OPENAPI_CLIENT_ID, + }, +) + +if settings.BACKEND_CORS_ORIGINS: + app.add_middleware( + CORSMiddleware, + allow_origins=[str(origin) for origin in settings.BACKEND_CORS_ORIGINS], + allow_credentials=True, + allow_methods=['*'], + allow_headers=['*'], + ) + +azure_scheme = B2CMultiTenantAuthorizationCodeBearer( + app_client_id=settings.APP_CLIENT_ID, + openid_config_url=f'https://{settings.TENANT_NAME}.b2clogin.com/{settings.TENANT_NAME}.onmicrosoft.com/{settings.AUTH_POLICY_NAME}/v2.0/.well-known/openid-configuration', + openapi_authorization_url=f'https://{settings.TENANT_NAME}.b2clogin.com/{settings.TENANT_NAME}.onmicrosoft.com/{settings.AUTH_POLICY_NAME}/oauth2/v2.0/authorize', + openapi_token_url=f'https://{settings.TENANT_NAME}.b2clogin.com/{settings.TENANT_NAME}.onmicrosoft.com/{settings.AUTH_POLICY_NAME}/oauth2/v2.0/token', + scopes={ + f'https://{settings.TENANT_NAME}.onmicrosoft.com/{settings.APP_CLIENT_ID}/user_impersonation': 'user_impersonation', + }, + validate_iss=False, +) + + +@app.on_event('startup') +async def load_config() -> None: + """ + Load OpenID config on startup. + """ + await azure_scheme.openid_config.load_config() + + +@app.get("/", dependencies=[Security(azure_scheme)]) +async def root(): + return {"message": "Hello World"} + + +if __name__ == '__main__': + uvicorn.run('main:app', reload=True) +``` + +## Testing it out + +Head over to your OpenAPI documentation at [http://localhost:8000/docs](http://localhost:8000/docs) and check out your API documentation. +You'll see a new button called `Authorize`. Before clicking it, try out your API to see that you're unauthorized. + +![fastapi_1_authorize_button](../../static/img/single-and-multi-tenant/fastapi_1_authorize_button.png) +![fastapi_2_not_authenticated](../../static/img/single-and-multi-tenant/fastapi_2_not_authenticated.png) + + +Now, let's authenticate. Click the **Authorize** button. Check your scope, and leave `Client secret` blank. You do not +need that with the PKCE flow. + +![fastapi_3_authenticate](../../static/img/single-and-multi-tenant/fastapi_3_authenticate.png) + + +Consent to the permissions requested: + +![fastapi_4_consent](../../static/img/single-and-multi-tenant/fastapi_4_consent.png) + + +:::info +If you get a warning that your redirect URL is wrong, you're probably using `127.0.0.1` instead of `localhost` +::: + +Try out your API again to see that it works! + +### Last thing.. + +As discussed earlier, there is a `scope` parameter to the `Security()` version of `Depends()`. If you'd want to lock down +your API to only be accessible by those with certain scopes, you can simply pass it into the dependency. + +```python +@app.get("/", dependencies=[Security(azure_scheme, scopes=['wrong_scope'])]) +``` +If you do this and try out your API again, you'll see that you're denied. + + +Your users are now safe and secure! Good luck! 🔒🚀 + +