Stay Hungry,Stay Foolish!

fastapi-azure-auth

fastapi-azure-auth

https://github.com/intility/fastapi-azure-auth

At Intility we use FastAPI for both internal (single-tenant) and customer-facing (multi-tenant) APIs. This package enables our developers (and you 😊) to create features without worrying about authentication and authorization.

Also, we're hiring!

📚 Resources

The documentation contains a full tutorial on how to configure Azure Entra ID 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.

MIT License | Documentation | GitHub

 

https://intility.github.io/fastapi-azure-auth/usage-and-faq/locking_down_on_roles

security.py
from typing import Annotated
from fastapi import Depends
from fastapi_azure_auth.exceptions import Unauthorized
from fastapi_azure_auth.user import User

async def validate_is_admin_user(user: User = Depends(azure_scheme)) -> None:
"""
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:
raise Unauthorized('User is not an AdminUser')

AdminUser = Annotated[User, Depends(validate_is_admin_user)]
 

and in your view:

my_view.py
@app.get("/items/")
def read_items(user: AdminUser):
...

 

entire code

https://github.com/henry-richard7/Fastapi-Azure-EntraID-Auth/tree/main

from fastapi import FastAPI, Depends
import uvicorn
from fastapi_azure_auth import SingleTenantAzureAuthorizationCodeBearer

from fastapi_azure_auth.exceptions import InvalidAuth
from fastapi_azure_auth.user import User

from fastapi import FastAPI

from pydantic import AnyHttpUrl, computed_field
from pydantic_settings import BaseSettings
from fastapi.middleware.cors import CORSMiddleware

from contextlib import asynccontextmanager
from typing import AsyncGenerator

from typing import Union


class Settings(BaseSettings):
    BACKEND_CORS_ORIGINS: list[Union[str, AnyHttpUrl]] = ["http://localhost:8000"]
    OPENAPI_CLIENT_ID: str = ""
    APP_CLIENT_ID: str = ""
    TENANT_ID: str = ""
    SCOPE_DESCRIPTION: str = ""

    @computed_field
    @property
    def SCOPE_NAME(self) -> str:
        return f"api://{self.APP_CLIENT_ID}/{self.SCOPE_DESCRIPTION}"

    @computed_field
    @property
    def SCOPES(self) -> dict:
        return {
            self.SCOPE_NAME: self.SCOPE_DESCRIPTION,
        }

    @computed_field
    @property
    def OPENAPI_AUTHORIZATION_URL(self) -> str:
        return (
            f"https://login.microsoftonline.com/{self.TENANT_ID}/oauth2/v2.0/authorize"
        )

    @computed_field
    @property
    def OPENAPI_TOKEN_URL(self) -> str:
        return f"https://login.microsoftonline.com/{self.TENANT_ID}/oauth2/v2.0/token"

    class Config:
        env_file = ".env"
        env_file_encoding = "utf-8"
        case_sensitive = True


settings = Settings()


@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
    """
    Load OpenID config on startup.
    """
    await azure_scheme.openid_config.load_config()
    yield


app = FastAPI(
    swagger_ui_oauth2_redirect_url="/oauth2-redirect",
    swagger_ui_init_oauth={
        "usePkceWithAuthorizationCodeGrant": True,
        "clientId": settings.OPENAPI_CLIENT_ID,
        "scopes": settings.SCOPE_NAME,
    },
)

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 = SingleTenantAzureAuthorizationCodeBearer(
    app_client_id=settings.APP_CLIENT_ID,
    tenant_id=settings.TENANT_ID,
    scopes=settings.SCOPES,
)


@app.get("/")
async def root(user: User = Depends(azure_scheme)):
    print(user.roles)
    return {"message": "Hello World"}


@app.get("/me")
async def me(user: User = Depends(azure_scheme)):
    return user.model_dump()


@app.delete("/delete")
async def delete_data(id: int, user: User = Depends(azure_scheme)):
    if "API.Admins" not in user.roles:
        raise InvalidAuth("User is not authorized to access this endpoint.")
    else:
        return {"message": "delete called."}


if __name__ == "__main__":
    uvicorn.run("main:app", reload=True)

 

 

https://learn.microsoft.com/en-us/entra/external-id/customers/how-to-use-app-roles-customers

https://learn.microsoft.com/en-us/entra/identity-platform/howto-add-app-roles-in-apps

https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app

 

posted @ 2025-04-27 23:29  lightsong  阅读(23)  评论(0)    收藏  举报
千山鸟飞绝,万径人踪灭