From a70d053fed6f25f48cd582de02777f3b0408645b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Kr=C3=BCger=20Svensson?= Date: Wed, 1 Sep 2021 12:39:54 +0200 Subject: [PATCH 1/4] Rewrite README.md for 3.0.0 release --- README.md | 150 +++++++++++++++++++----------------------------------- 1 file changed, 52 insertions(+), 98 deletions(-) diff --git a/README.md b/README.md index c74d6ea..60ee85e 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,10 @@

Azure AD Authentication for FastAPI apps made easy.

+

+ + +

Python version @@ -33,6 +37,7 @@ isort +

@@ -47,13 +52,19 @@ This package enables our developers (and you 😊) to create features without wo Also, [we're hiring!](https://intility.no/en/career/) -## ⚡️ Quick start -### Azure -Azure docs will be available when create-fastapi-app is developed. In the meantime -please use the [.NET](https://create.intility.app/dotnet/setup/authorization) documentation. +## 📚 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 +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. -### FastAPI +[**MIT License**](https://github.com/Intility/fastapi-azure-auth/blob/main/LICENSE) +| [**Documentation**](https://intility.github.io/fastapi-azure-auth/) +| [**GitHub**](https://github.com/snok/django-guid) + + +## ⚡ TL;DR #### 1. Install this library: ```bash @@ -71,133 +82,76 @@ app = FastAPI( ... swagger_ui_oauth2_redirect_url='/oauth2-redirect', swagger_ui_init_oauth={ - 'usePkceWithAuthorizationCodeGrant': True, - 'clientId': settings.OPENAPI_CLIENT_ID # SPA app with grants to your app + 'usePkceWithAuthorizationCodeGrant': True, + 'clientId': settings.OPENAPI_CLIENT_ID, }, ) ``` #### 3. Setup CORS -Ensure you have CORS enabled for your local environment, such as `http://localhost:8000`. See [main.py](main.py) -and the `BACKEND_CORS_ORIGINS` in [config.py](demoproj/core/config.py) +Ensure you have CORS enabled for your local environment, such as `http://localhost:8000`. -#### 4. Configure the `AzureAuthorizationCodeBearer` -You _can_ do this in `main.py`, but it's recommended to put it -in your `dependencies.py` file instead, as this will avoid circular imports later. -See the [demo project](demoproj/api/api_v1/endpoints/hello_world.py) and read the official documentation -on [bigger applications](https://fastapi.tiangolo.com/tutorial/bigger-applications/) +#### 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). ```python # file: demoproj/api/dependencies.py -from fastapi_azure_auth.auth import AzureAuthorizationCodeBearer +from fastapi_azure_auth.auth import SingleTenantAzureAuthorizationCodeBearer -azure_scheme = AzureAuthorizationCodeBearer( - app=app, - app_client_id=settings.APP_CLIENT_ID, # Web app +azure_scheme = SingleTenantAzureAuthorizationCodeBearer( + app_client_id=settings.APP_CLIENT_ID, + tenant_id=settings.TENANT_ID, scopes={ - f'api://{settings.APP_CLIENT_ID}/user_impersonation': 'User Impersonation', - }, + f'api://{settings.APP_CLIENT_ID}/user_impersonation': 'user_impersonation', + } ) ``` +or for multi-tenant applications: +```python +# file: demoproj/api/dependencies.py +from fastapi_azure_auth.auth import MultiTenantAzureAuthorizationCodeBearer +azure_scheme = MultiTenantAzureAuthorizationCodeBearer( + app_client_id=settings.APP_CLIENT_ID, + scopes={ + f'api://{settings.APP_CLIENT_ID}/user_impersonation': 'user_impersonation', + }, + validate_iss=False +) +``` +To validate the `iss`, configure an +[`iss_callable`](https://intility.github.io/fastapi-azure-auth/multi-tenant/accept_specific_tenants_only). #### 5. Configure dependencies -Set your `intility_scheme` as a dependency for your wanted views/routers: - +Add `azure_scheme` as a dependency for your views/routers, using either `Security()` or `Depends()`. ```python # file: main.py from demoproj.api.dependencies import azure_scheme -app.include_router(api_router, prefix=settings.API_V1_STR, dependencies=[Depends(azure_scheme)]) +app.include_router(api_router, prefix=settings.API_V1_STR, dependencies=[Security(azure_scheme, scopes='user_impersonation')]) ``` #### 6. Load config on startup -This is optional but recommended. This will ensure the app crashes if something is misconfigured on startup (instead of when someone tries to do a request), and -ensures the first request don't have to wait for the provider config to load. +Optional but recommended. ```python # file: main.py -from fastapi_azure_auth.provider_config import provider_config - @app.on_event('startup') async def load_config() -> None: """ - Load config on startup. + Load OpenID config on startup. """ - await provider_config.load_config() -``` - - -## ⚙️ Configuration -For those using a non-Intility tenant, you also need to make changes to the `provider_config` to match -your tenant ID. You can do this in your previously created `load_config()` function. - -```python -# file: main.py -from fastapi_azure_auth.provider_config import provider_config - -@app.on_event('startup') -async def load_config() -> None: - provider_config.tenant_id = 'my-own-tenant-id' - await provider_config.load_config() + await azure_scheme.openid_config.load_config() ``` -If you want, you can deny guest users to access your API by passing the `allow_guest_users=False` -to `AzureAuthorizationCodeBearer`: - -```python -# file: demoproj/api/dependencies.py -azure_scheme = AzureAuthorizationCodeBearer( - ... - allow_guest_users=False -) -``` - -## 💡 Nice to knows - -#### User object -A `User` object is attached to the request state if the token is valid. Unparsed claims can be accessed at -`request.state.user.claims`. - -```python -# file: demoproj/api/api_v1/endpoints/hello_world.py -from fastapi_azure_auth.user import User -from fastapi import Request - -@router.get(...) -async def world(request: Request) -> dict: - user: User = request.state.user - return {'user': user} -``` - - -#### Permission checking -You often want to check that a user has a role or using a specific scope. This -can be done by creating your own dependency, which depends on `azure_scheme`. The `azure_scheme` dependency -returns a [`fastapi_azure_auth.user.User`](fastapi_azure_auth/user.py) object. - -Create your new dependency, which checks that the user has the correct role (in this case the -`AdminUser`-role): - -```python -# file: demoproj/api/dependencies.py -from fastapi import Depends -from fastapi_azure_auth.auth import InvalidAuth -from fastapi_azure_auth.user import User - -async def validate_is_admin_user(user: User = Depends(azure_scheme)) -> None: - """ - Validated that a user is in the `AdminUser` role in order to access the API. - Raises a 401 authentication error if not. - """ - if 'AdminUser' not in user.roles: - raise InvalidAuth('User is not an AdminUser') -``` - -Add the new dependency on either your route or on the API, as we've -done in our [demo project](demoproj/api/api_v1/endpoints/hello_world.py). +## 📄 Example OpenAPI documentation +Your OpenAPI documentation will get an `Authorize` button, which can be used to authenticate. +![authorize](docs/static/img/single-and-multi-tenant/fastapi_1_authorize_button.png) +The user can select which scopes to authenticate with, based on your configuration. +![scopes](docs/static/img/single-and-multi-tenant/fastapi_3_authenticate.png) \ No newline at end of file From 68c63078dc6dbfd537694c04b73483f280d901c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Kr=C3=BCger=20Svensson?= Date: Wed, 1 Sep 2021 12:50:08 +0200 Subject: [PATCH 2/4] Fix titles in docs Version 3.0.0 --- docs/docs/multi-tenant/azure_setup.mdx | 2 +- docs/docs/multi-tenant/locking_down_on_roles.mdx | 2 +- docs/docs/single-tenant/azure_setup.mdx | 2 +- docs/docs/single-tenant/locking_down_on_roles.mdx | 2 +- fastapi_azure_auth/__init__.py | 2 +- pyproject.toml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/docs/multi-tenant/azure_setup.mdx b/docs/docs/multi-tenant/azure_setup.mdx index 75849d9..3a1470d 100644 --- a/docs/docs/multi-tenant/azure_setup.mdx +++ b/docs/docs/multi-tenant/azure_setup.mdx @@ -1,5 +1,5 @@ --- -title: AAD Authorization +title: Azure configuration sidebar_position: 1 --- diff --git a/docs/docs/multi-tenant/locking_down_on_roles.mdx b/docs/docs/multi-tenant/locking_down_on_roles.mdx index d9af973..67f62af 100644 --- a/docs/docs/multi-tenant/locking_down_on_roles.mdx +++ b/docs/docs/multi-tenant/locking_down_on_roles.mdx @@ -12,7 +12,7 @@ from fastapi_azure_auth.user import User async def validate_is_admin_user(user: User = Depends(azure_scheme)) -> None: """ - Validated that a user is in the `AdminUser` role in order to access the API. + Validate that a user is in the `AdminUser` role in order to access the API. Raises a 401 authentication error if not. """ if 'AdminUser' not in user.roles: diff --git a/docs/docs/single-tenant/azure_setup.mdx b/docs/docs/single-tenant/azure_setup.mdx index 8167ba5..f8fa906 100644 --- a/docs/docs/single-tenant/azure_setup.mdx +++ b/docs/docs/single-tenant/azure_setup.mdx @@ -1,5 +1,5 @@ --- -title: AAD Authorization +title: Azure configuration sidebar_position: 1 --- diff --git a/docs/docs/single-tenant/locking_down_on_roles.mdx b/docs/docs/single-tenant/locking_down_on_roles.mdx index d9af973..67f62af 100644 --- a/docs/docs/single-tenant/locking_down_on_roles.mdx +++ b/docs/docs/single-tenant/locking_down_on_roles.mdx @@ -12,7 +12,7 @@ from fastapi_azure_auth.user import User async def validate_is_admin_user(user: User = Depends(azure_scheme)) -> None: """ - Validated that a user is in the `AdminUser` role in order to access the API. + Validate that a user is in the `AdminUser` role in order to access the API. Raises a 401 authentication error if not. """ if 'AdminUser' not in user.roles: diff --git a/fastapi_azure_auth/__init__.py b/fastapi_azure_auth/__init__.py index b36a090..22b2b3f 100644 --- a/fastapi_azure_auth/__init__.py +++ b/fastapi_azure_auth/__init__.py @@ -3,4 +3,4 @@ SingleTenantAzureAuthorizationCodeBearer, ) -__version__ = '3.0.0-rc1' +__version__ = '3.0.0' diff --git a/pyproject.toml b/pyproject.toml index d875ae7..8d06124 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "fastapi-azure-auth" -version = "3.0.0-rc1" # Remember to change in __init__.py as well +version = "3.0.0" # Remember to change in __init__.py as well description = "Easy and secure implementation of Azure AD for your FastAPI APIs" authors = ["Jonas Krüger Svensson "] readme = "README.md" From e3b6e374a526675e305c66e6e8fa9820ad46c1b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Kr=C3=BCger=20Svensson?= Date: Wed, 1 Sep 2021 13:19:10 +0200 Subject: [PATCH 3/4] Docs for build and deployment of documentation --- docs/README.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/docs/README.md b/docs/README.md index a2a707f..83aa873 100644 --- a/docs/README.md +++ b/docs/README.md @@ -24,10 +24,15 @@ $ yarn build This command generates static content into the `build` directory and can be served using any static contents hosting service. -### Deployment +It's important that you build the documentation using `yarn build` **before pushing to `main`**. After building, +check that everything works, such as syntax highlighting etc. -``` -$ GIT_USER= USE_SSH=true yarn deploy -``` +If there are issues, please try +* to run `npm run clear` or `yarn clear` +* delete `package-lock.json` and re-.install packages + + +### Deployment -If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. +GitHub actions takes care of deployment. Any changes to the `docs` folder on the `main` branch will trigger +the pipeline. You can see the documentation live at https://intility.github.io/fastapi-azure-auth/ From 6080064881dfbdabf7dc22e6c4456f956268f41c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Kr=C3=BCger=20Svensson?= Date: Wed, 1 Sep 2021 14:37:40 +0200 Subject: [PATCH 4/4] Fix feedback on docs, thank you @sondrelg --- README.md | 7 ++++++- docs/README.md | 3 ++- docs/docs/installation.mdx | 2 +- docs/docs/multi-tenant/azure_setup.mdx | 4 ++-- docs/docs/multi-tenant/fastapi_configuration.mdx | 2 +- docs/docs/single-tenant/azure_setup.mdx | 4 ++-- docs/docs/single-tenant/fastapi_configuration.mdx | 2 +- docs/docs/single-tenant/guest_users.mdx | 4 ++-- 8 files changed, 17 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 60ee85e..262ae0b 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,12 @@ application exactly how it's described there, and then alter it to your needs la | [**GitHub**](https://github.com/snok/django-guid) -## ⚡ TL;DR +## ⚡ Setup + +This is a tl;dr intended to give you an idea of what this package does and how to use it. +For a more in-depth tutorial and settings reference you should read the +[documentation](https://intility.github.io/fastapi-azure-auth/). + #### 1. Install this library: ```bash diff --git a/docs/README.md b/docs/README.md index 83aa873..061ac57 100644 --- a/docs/README.md +++ b/docs/README.md @@ -35,4 +35,5 @@ If there are issues, please try ### Deployment GitHub actions takes care of deployment. Any changes to the `docs` folder on the `main` branch will trigger -the pipeline. You can see the documentation live at https://intility.github.io/fastapi-azure-auth/ +the pipeline. You can see the documentation live at https://intility.github.io/fastapi-azure-auth/, and browse +the static files in the `gh-pages` branch. \ No newline at end of file diff --git a/docs/docs/installation.mdx b/docs/docs/installation.mdx index bc0cdce..1a00b50 100644 --- a/docs/docs/installation.mdx +++ b/docs/docs/installation.mdx @@ -12,7 +12,7 @@ poetry add fastapi-azure-auth ``` :::info -At the moment Python 3.9+ is only supported. If you can't install the package, check your Python version. +Only Python 3.9 and above is currently supported. If you can't install the package, check your Python version. ::: Now that it's installed, jump on over to the single or multi-tenant application, based on what you need. diff --git a/docs/docs/multi-tenant/azure_setup.mdx b/docs/docs/multi-tenant/azure_setup.mdx index 3a1470d..aa8f6a9 100644 --- a/docs/docs/multi-tenant/azure_setup.mdx +++ b/docs/docs/multi-tenant/azure_setup.mdx @@ -3,8 +3,8 @@ title: Azure configuration sidebar_position: 1 --- -For Azure AD to work for both our backend and through our OpenAPI (swagger) documentation, we'll need to do create -two application registrations. +For Azure AD authentication to cover both direct API use, and usage from the OpenAPI (swagger) documentation, +we'll need to do create two application registrations. We'll start with the API. diff --git a/docs/docs/multi-tenant/fastapi_configuration.mdx b/docs/docs/multi-tenant/fastapi_configuration.mdx index e8bee59..d991686 100644 --- a/docs/docs/multi-tenant/fastapi_configuration.mdx +++ b/docs/docs/multi-tenant/fastapi_configuration.mdx @@ -83,7 +83,7 @@ if __name__ == '__main__': ## Configure `CORS` -Now, let's configure our `CORS`. Without them your OpenAPI docs won't work as expected: +Now, let's configure our `CORS`. Without `CORS` your OpenAPI docs won't work as expected: ```python {5,24-31} title="main.py" from typing import Union diff --git a/docs/docs/single-tenant/azure_setup.mdx b/docs/docs/single-tenant/azure_setup.mdx index f8fa906..9fb92ee 100644 --- a/docs/docs/single-tenant/azure_setup.mdx +++ b/docs/docs/single-tenant/azure_setup.mdx @@ -3,8 +3,8 @@ title: Azure configuration sidebar_position: 1 --- -For Azure AD to work for both our backend and through our OpenAPI (swagger) documentation, we'll need to do create -two application registrations. +For Azure AD authentication to cover both direct API use, and usage from the OpenAPI (swagger) documentation, +we'll need to do create two application registrations. We'll start with the API. diff --git a/docs/docs/single-tenant/fastapi_configuration.mdx b/docs/docs/single-tenant/fastapi_configuration.mdx index d1afd92..d0187c8 100644 --- a/docs/docs/single-tenant/fastapi_configuration.mdx +++ b/docs/docs/single-tenant/fastapi_configuration.mdx @@ -85,7 +85,7 @@ if __name__ == '__main__': ## Configure `CORS` -Now, let's configure our `CORS`. Without them your OpenAPI docs won't work as expected: +Now, let's configure our `CORS`. Without `CORS` your OpenAPI docs won't work as expected: ```python {5,26-33} title="main.py" from typing import Union diff --git a/docs/docs/single-tenant/guest_users.mdx b/docs/docs/single-tenant/guest_users.mdx index bfb50de..05e7827 100644 --- a/docs/docs/single-tenant/guest_users.mdx +++ b/docs/docs/single-tenant/guest_users.mdx @@ -4,8 +4,8 @@ sidebar_position: 3 --- By default, guest users in your tenant will be able to access your APIs. You can block this in code by checking -the users `tid` is equal to yours, or preferably by configuring Azure AD. If you're using Azure AD, the user -will be denied to sign in, instead of getting a 403 when he's fetches his token. +the users `tid` is equal to yours, or preferably by configuring Azure AD. If you're using Azure AD to limit +guest user access, the user will be denied access on sign in, instead of getting a 403 when using the API. Go to **all** your [Enterprise Applications](https://portal.azure.com/#blade/Microsoft_AAD_IAM/StartboardApplicationsMenuBlade/AllApps/menuId/) and do the following steps. You can find your Enterprise Application either by searching on the Client ID in the