Skip to content

Commit

Permalink
Merge branch 'main' of github.com:Intility/intility-auth-fastapi into…
Browse files Browse the repository at this point in the history
… main
  • Loading branch information
JonasKs committed Sep 1, 2021
2 parents 0039ef8 + 5b51d81 commit 119514f
Show file tree
Hide file tree
Showing 11 changed files with 82 additions and 117 deletions.
155 changes: 57 additions & 98 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
<p align="center">
<em>Azure AD Authentication for FastAPI apps made easy.</em>
</p>
<p align="center">
<img src="https://img.shields.io/badge/Single--tenant-Supported-blue?logo=Microsoft%20Azure&logoColor=white">
<img src="https://img.shields.io/badge/Multi--tenant-Supported-blue?logo=Microsoft%20Azure&logoColor=white">
</p>
<p align="center">
<a href="https://python.org">
<img src="https://img.shields.io/badge/python-v3.9+-blue.svg?logo=python&logoColor=white&label=python" alt="Python version">
Expand Down Expand Up @@ -33,6 +37,7 @@
<a href="https://pycqa.github.io/isort/">
<img src="https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336" alt="isort">
</a>

</p>


Expand All @@ -47,13 +52,24 @@ 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.

[**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)


## ⚡ 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/).

### FastAPI

#### 1. Install this library:
```bash
Expand All @@ -71,133 +87,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()
```


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
)
await azure_scheme.openid_config.load_config()
```

## 💡 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)
16 changes: 11 additions & 5 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,16 @@ $ 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=<Your GitHub username> 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/, and browse
the static files in the `gh-pages` branch.
2 changes: 1 addition & 1 deletion docs/docs/installation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
6 changes: 3 additions & 3 deletions docs/docs/multi-tenant/azure_setup.mdx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
---
title: AAD Authorization
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.

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/multi-tenant/fastapi_configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/multi-tenant/locking_down_on_roles.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
6 changes: 3 additions & 3 deletions docs/docs/single-tenant/azure_setup.mdx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
---
title: AAD Authorization
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.

Expand Down
4 changes: 2 additions & 2 deletions docs/docs/single-tenant/guest_users.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/single-tenant/locking_down_on_roles.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion fastapi_azure_auth/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
SingleTenantAzureAuthorizationCodeBearer,
)

__version__ = '3.0.0-rc1'
__version__ = '3.0.0'
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -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 <jonas.svensson@intility.no>"]
readme = "README.md"
Expand Down

0 comments on commit 119514f

Please sign in to comment.