FastAPI client library for the mcp-mesh auth platform (JWT + UMA permission aggregation).
Project description
mcpmesh-auth-lib
FastAPI client library for the mcp-mesh auth platform. Python equivalent of
libs/auth-lib/ (the Spring Boot / Java auth-lib v2). Validates Keycloak-issued
JWTs, aggregates UMA permissions across one-or-more resource-server audiences,
and ships a Pydantic MeResponse model that matches the Java DTO byte-for-byte.
Install
# from a sibling repo / monorepo path
pip install -e /path/to/auth-manager/libs/auth-lib-python
# or via git URL (private use)
pip install "mcpmesh-auth-lib @ git+https://github.com/your-org/auth-manager@main#subdirectory=libs/auth-lib-python"
Requires Python 3.11+.
Environment variables
| Var | Required | Default | Notes |
|---|---|---|---|
AUTH_LIB_ISSUER_URI |
yes | e.g. https://auth.mcp-mesh.io/auth/realms/t-app1 |
|
AUTH_LIB_CLIENT_ID |
yes | This app's OIDC client_id; used as default audience | |
AUTH_LIB_CLIENT_SECRET |
no | None |
Confidential client secret (only used by callers that need extra KC calls; not used for UMA itself) |
AUTH_LIB_AUDIENCES |
no | [client_id] |
Comma-separated list. Extra resource servers to aggregate UMA permissions across. |
AUTH_LIB_JWKS_CACHE_TTL_SECONDS |
no | 3600 |
How long PyJWKClient caches JWKS keys |
AUTH_LIB_PERMISSION_CACHE_TTL_SECONDS |
no | 60 |
UMA permission cache TTL |
AUTH_LIB_REDIS_URL |
no | None |
e.g. redis://redis:6379/0. If set, permissions cache uses Redis. |
AUTH_LIB_HTTP_TIMEOUT_SECONDS |
no | 5.0 |
HTTP timeout for JWKS + UMA calls. |
AUTH_LIB_PERMISSIONS_SOURCE |
no | claims |
claims reads perms from JWT resource_access.<client>.roles; uma calls KC's UMA endpoint. See below. |
AUTH_LIB_PERMISSIONS_SOURCE
Controls where atomic permissions come from when Permissions.all_for(token, claims)
is called.
claims(default) — reads from the JWT'sresource_access.<client>.rolesclaim for each configured audience (AUTH_LIB_AUDIENCES, falling back toAUTH_LIB_CLIENT_ID). This is the auth-manager pattern: atomic permissions are KC client roles on the backend client, composite realm roles bundle them, and KC's role expansion flattens them into the token at mint time. No round-trip to KC is needed — the perms are already in the token. Typical when you've onboarded the tenant withmanifest:applyand have a permission catalog defined.uma— calls Keycloak's UMA ticket-grant endpoint (the originalPermissionsbehavior). Only needed for legacy tenants that still have KC Authorization Services (resources/scopes/policies/permissions) configured on their backend client. UMA is being phased out across the platform; a matching Java auth-lib migration is on the backlog.
The selection is wired up by auth_lib_init(app). Pass an explicit
permissions= instance to override (e.g., a custom subclass).
Quickstart
from fastapi import Depends, FastAPI
from mcpmesh_auth_lib import (
Tenant, auth_lib_init, build_me_response,
current_user, require_permission,
)
app = FastAPI()
auth_lib_init(app) # reads AUTH_LIB_* env vars
TENANT = Tenant(id="t-1", slug="app1", display_name="App One", realm_name="t-app1")
@app.get("/api/me")
def me(claims: dict = Depends(current_user)):
return build_me_response(
claims,
app.state.auth_lib_permissions,
TENANT,
raw_token=claims["_raw_token"],
).model_dump(by_alias=True)
@app.get("/api/orders", dependencies=[Depends(require_permission("ORDER_VIEW"))])
def orders():
return [{"id": 1, "item": "Widget", "qty": 3}]
A working version of this is in examples/fastapi_minimal/.
API surface
from mcpmesh_auth_lib import (
AuthLibSettings, # pydantic-settings: reads AUTH_LIB_* env vars
auth_lib_init, # one-call setup: auth_lib_init(app)
current_user, # Depends(): claims dict (raises 401 on bad/missing token)
optional_user, # Depends(): claims | None
require_permission, # Depends-factory: require_permission("ORDER_VIEW")
require_any_permission, # Depends-factory: require_any_permission("ORDER_VIEW", "ORDER_APPROVE")
require_role, # Depends-factory: require_role("tenant-admin", client="usermanagement")
Permissions, # service: UMA-based .all_for(token, claims) -> Set[str]
ClaimRolesPermissions, # service: claims-based (default); reads resource_access.<client>.roles
JwtValidator, # service: .decode_and_verify(token) -> dict
JwtValidationError,
MeResponse, User, Tenant,
build_me_response,
)
Permission strings
UMA responses (response_mode=permissions) come back as
[{rsname, scopes: [...]}]. The lib flattens these to
<RSNAME>_<SCOPE> uppercased with _ separator. So an "order" resource with a
"view" scope is exposed as the permission string ORDER_VIEW. This matches the
React auth-lib-react usePermission("ORDER_VIEW") hook and the Java side's
Permissions.all_for(jwt) set semantics.
(The Java side ALSO publishes PERMISSION_ORDER_VIEW-prefixed Spring authorities
for @PreAuthorize("hasAuthority('PERMISSION_ORDER_VIEW')") style checks. The
Python require_permission(...) factory uses the same un-prefixed strings as
Permissions.all_for for ergonomics — no PERMISSION_ prefix needed.)
/me payload shape
{
"user": {
"id": "uuid",
"email": "alice@app1.test",
"preferredUsername": "alice@app1.test",
"name": "Alice Tester"
},
"context": "tenant",
"tenant": {
"id": "t-1",
"slug": "app1",
"displayName": "App One",
"realmName": "t-app1"
},
"isPlatformAdmin": false,
"isTenantAdmin": false,
"permissions": ["ORDER_VIEW", "ORDER_APPROVE"]
}
This is the same JSON the Java app1-backend returns — auth-lib-react can
decode either source with one helper.
Differences vs. Java auth-lib v2
- Single-realm. Just like the Java side: one
AuthLibSettingsper app. - Multi-audience UMA aggregation. Set
AUTH_LIB_AUDIENCES=orders,invoicesto pull UMA permissions from multiple resource servers in one /me response. - Permission decorator names. The Python
require_permission("ORDER_VIEW")uses the un-prefixed permission string. The Java side has bothPermissions#has(jwt, "ORDER_VIEW")(un-prefixed) AND@PreAuthorize("hasAuthority('PERMISSION_ORDER_VIEW')")(prefixed). Python only ships the un-prefixed flavor — there's no SpringGrantedAuthorityequivalent to mirror in FastAPI. - Sync only. v1 uses
httpx.Client+redis-py(sync). Async callers can wrapPermissions.all_forinrun_in_executorif needed; an async variant may land in a later release. - No Spring autoconfig. Call
auth_lib_init(app)explicitly in your FastAPI app's startup. (Spring Boot magic doesn't have a Python analogue.)
Testing
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
pytest -v
Tests use respx to mock both the JWKS and UMA endpoints — no Keycloak needed.
License
Apache-2.0.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mcp_mesh_auth_lib-0.1.1.tar.gz.
File metadata
- Download URL: mcp_mesh_auth_lib-0.1.1.tar.gz
- Upload date:
- Size: 21.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
63ed75a4598aa2e75940eb36b2610090b0bc6b150c168b363d9d9037ce2dde67
|
|
| MD5 |
2e64a0c024d53ee5cb9943059b84c56e
|
|
| BLAKE2b-256 |
651ddedc55d9db6ad3bef26402e1ae116478411adf0598d91f86342c852d953c
|
File details
Details for the file mcp_mesh_auth_lib-0.1.1-py3-none-any.whl.
File metadata
- Download URL: mcp_mesh_auth_lib-0.1.1-py3-none-any.whl
- Upload date:
- Size: 18.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
438f36a3ed38bb02a5fac64c7737b28a8935d013c1f6f0698612586c2a325e40
|
|
| MD5 |
537f695933b6c8db8c505af8ac77be49
|
|
| BLAKE2b-256 |
f8b85f57f3fefe7022774dcad7e94aac5a75eab0980978aa4d79975292b2eb00
|