Skip to content

cleanenergyexchange/fastapi-zitadel-auth

Repository files navigation

FastAPI Zitadel Auth

Simplify OAuth2 authentication in FastAPI apps using Zitadel as the identity service, including token validation, role-based access control, and Swagger UI integration.

Test status Code coverage Package version Python versions License

Features

  • Authorization Code flow with PKCE
  • JWT validation using Zitadel JWKS
  • Role-based access control using Zitadel roles
  • Service user authentication (JWT Profile)
  • Swagger UI integration
  • Type-safe token validation

Note

This library implements JWT, locally validated using JWKS, as it prioritizes performance, see Zitadel docs on Opaque tokens vs JWT. If you need to validate opaque tokens using Introspection, please open an issue – PRs are welcome!

Installation and quick start

pip install fastapi-zitadel-auth
from fastapi import FastAPI, Security
from fastapi_zitadel_auth import ZitadelAuth, AuthConfig

auth = ZitadelAuth(AuthConfig(
    client_id="your-client-id",
    project_id="your-project-id",
    zitadel_host="https://your-instance.zitadel.cloud"
))

app = FastAPI(
    swagger_ui_init_oauth={
        "usePkceWithAuthorizationCodeGrant": True,
        "clientId": 'your-client-id',
        "scopes": "openid profile email urn:zitadel:iam:org:project:id:zitadel:aud urn:zitadel:iam:org:projects:roles"
    }
)


@app.get("/protected", dependencies=[Security(auth)])
def protected_route():
    return {"message": "Access granted!"}

See the Usage section for more details.

Usage

Configuration

Zitadel

Set up a project in Zitadel according to docs/ZITADEL_SETUP.md.

FastAPI

from fastapi import FastAPI, Request, Security
from fastapi_zitadel_auth import ZitadelAuth, AuthConfig

# Your Zitadel configuration
CLIENT_ID = 'your-zitadel-client-id'
PROJECT_ID = 'your-zitadel-project-id'
BASE_URL = 'https://your-instance-xyz.zitadel.cloud'

# Create an AuthConfig object with your Zitadel configuration
config = AuthConfig(
    client_id=CLIENT_ID,
    project_id=PROJECT_ID,
    zitadel_host=BASE_URL,
    scopes={
        "openid": "OpenID Connect",
        "email": "Email",
        "profile": "Profile",
        "urn:zitadel:iam:org:project:id:zitadel:aud": "Audience",
        "urn:zitadel:iam:org:projects:roles": "Roles",
    },
)

# Create a ZitadelAuth object with the AuthConfig usable as a FastAPI dependency
auth = ZitadelAuth(config)

# Create a FastAPI app and configure Swagger UI
app = FastAPI(
    title="fastapi-zitadel-auth demo",
    swagger_ui_oauth2_redirect_url="/oauth2-redirect",
    swagger_ui_init_oauth={
        "usePkceWithAuthorizationCodeGrant": True,
        "clientId": CLIENT_ID,
        "scopes": " ".join(
            [
                "openid",
                "email",
                "profile",
                "urn:zitadel:iam:org:project:id:zitadel:aud",
                "urn:zitadel:iam:org:projects:roles",
            ]
        ),
    },
)


# Create an endpoint and protect it with the ZitadelAuth dependency
@app.get(
    "/api/private",
    summary="Private endpoint, requiring a valid token with `system` scope",
    dependencies=[Security(auth, scopes=["system"])],
)
def private(request: Request):
    return {
        "message": f"Hello, protected world! Here is Zitadel user {request.state.user.user_id}"
    }

Demo app

See demo_project for a complete example, including service user login. To run the demo app:

uv run demo_project/server.py

Then navigate to http://localhost:8001/docs to see the Swagger UI.

Service user

Service users are "machine users" in Zitadel.

To log in as a service user, change the config in demo_project/service_user.py, then

uv run demo_project/service_user.py

Make sure you have a running server at http://localhost:8001.

Development

See docs/CONTRIBUTING.md for development instructions.