FastAPI-Azure-auth
Azure AD Authentication for FastAPI apps made easy.
🚀
Description
FastAPI is a modern, fast (high-performance), web framework for building APIs with Python, based on standard Python type hints.
At Intility, FastAPI is a popular framework among its developers, with customer-facing and internal services developed entirely on a FastAPI backend.
This package enables our developers (and you
Also, we're hiring!
⚡️
Quick start
Azure
Azure docs will be available when create-fastapi-app is developed. In the meantime please use the .NET documentation.
FastAPI
1. Install this library:
pip install fastapi-azure-auth
# or
poetry add fastapi-azure-auth
2. Configure your FastAPI app
Include swagger_ui_oauth2_redirect_url
and swagger_ui_init_oauth
in your FastAPI app initialization:
# file: main.py
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
},
)
3. Setup CORS
Ensure you have CORS enabled for your local environment, such as http://localhost:8000
. See main.py and the BACKEND_CORS_ORIGINS
in config.py
AzureAuthorizationCodeBearer
4. Configure the 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 and read the official documentation on bigger applications
# file: demoproj/api/dependencies.py
from fastapi_azure_auth.auth import AzureAuthorizationCodeBearer
azure_scheme = AzureAuthorizationCodeBearer(
app=app,
app_client_id=settings.APP_CLIENT_ID, # Web app
scopes={
f'api://{settings.APP_CLIENT_ID}/user_impersonation': 'User Impersonation',
},
)
5. Configure dependencies
Set your intility_scheme
as a dependency for your wanted views/routers:
# file: main.py
from demoproj.api.dependencies import azure_scheme
app.include_router(api_router, prefix=settings.API_V1_STR, dependencies=[Depends(azure_scheme)])
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.
# 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.
"""
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.
# 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
:
# 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
.
# 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
object.
Create your new dependency, which checks that the user has the correct role (in this case the AdminUser
-role):
# 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.