EasyAuth Claims Extraction¶
Trigger: HTTP | State: stateless | Guarantee: at-most-once | Difficulty: intermediate
Overview¶
This recipe demonstrates how to extract and use Azure App Service Authentication
(EasyAuth) claims in an Azure Functions Python v2 application. When EasyAuth is
enabled, authenticated requests arrive with the X-MS-CLIENT-PRINCIPAL header
containing a base64-encoded JSON payload with the identity provider type, claim
type metadata, and an array of user claims.
The example decodes this header, extracts claims into a usable format, and
implements role-based access control using the roles claim.
When to Use¶
- Your Function App runs behind App Service Authentication (EasyAuth).
- You need to extract user identity (name, email, roles) from incoming requests.
- You want role-based access control without managing JWT validation yourself.
- You need a lightweight auth layer that complements — not replaces — EasyAuth.
When NOT to Use¶
- You are not running behind App Service Authentication and cannot trust injected EasyAuth headers.
- You need direct JWT validation for local containers, Kubernetes, or non-App Service hosting.
- You need cross-tenant policy enforcement beyond simple role checks.
Architecture¶
flowchart LR
client[Authenticated client]
easyauth[App Service EasyAuth]
routes[auth_me / auth_admin handlers]
service[auth_service helpers]
claims[Decoded claims + roles]
client --> easyauth --> routes --> service --> claims
Behavior¶
sequenceDiagram
participant Client
participant EasyAuth as App Service EasyAuth
participant Function as Azure Function
Client->>EasyAuth: Authenticated request
EasyAuth->>Function: Inject X-MS-CLIENT-PRINCIPAL header
Function->>Function: Decode principal and flatten claims
alt admin route with required role
Function->>Function: Check roles claim
end
Function-->>Client: 200/403 JSON response
Project Structure¶
examples/apis-and-ingress/auth_easyauth/
├── function_app.py
├── app/
│ ├── core/
│ │ └── logging.py
│ ├── functions/
│ │ └── auth.py
│ └── services/
│ └── auth_service.py
├── tests/
│ └── test_auth.py
├── host.json
├── local.settings.json.example
├── pyproject.toml
├── Makefile
└── README.md
Implementation¶
Decoding the principal header¶
The X-MS-CLIENT-PRINCIPAL header contains a base64-encoded JSON object with
auth_typ, name_typ, role_typ, and a claims array of {"typ": ..., "val": ...}
objects. This matches the Microsoft documentation:
def decode_client_principal(header_value: str | None) -> Principal | None:
if not header_value:
return None
try:
decoded = base64.b64decode(header_value)
parsed = json.loads(decoded)
except (ValueError, json.JSONDecodeError):
return None
if not isinstance(parsed, dict):
return None
# Validate structure and return typed Principal dict
...
The decoded payload has this structure:
{
"auth_typ": "aad",
"name_typ": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name",
"role_typ": "http://schemas.microsoft.com/ws/2008/06/identity/claims/role",
"claims": [
{"typ": "name", "val": "Alice"},
{"typ": "roles", "val": "admin"}
]
}
Extracting claims and roles¶
Claims are flattened from the array format into a dictionary. Role claims use
the roles or http://schemas.microsoft.com/ws/2008/06/identity/claims/role
type:
def extract_claims(principal: Principal) -> dict[str, str]:
return {c["typ"]: c["val"] for c in principal.get("claims", []) if c.get("typ")}
def get_roles(principal: Principal) -> list[str]:
role_types = ("roles", "http://schemas.microsoft.com/ws/2008/06/identity/claims/role")
return [c["val"] for c in principal.get("claims", []) if c.get("typ") in role_types and c.get("val")]
Role-based access control decorator¶
The require_role decorator enforces role checks before the handler runs:
@require_role("admin")
def get_admin_response(principal: Principal) -> ServiceResponse:
return {"message": "Welcome, admin!", ...}, 200
Route handlers¶
Two endpoints demonstrate the pattern:
GET /api/auth/me— returns decoded claims for any authenticated user.GET /api/auth/admin— requires theadminrole, returns 403 otherwise.
Run Locally¶
Prerequisites:
- Python 3.10+
- Azure Functions Core Tools v4
azure-functionspackage- App Service Authentication (EasyAuth) enabled on the Function App
cd examples/apis-and-ingress/auth_easyauth
python -m venv .venv
source .venv/bin/activate
pip install -e .
cp local.settings.json.example local.settings.json
func start
Note: Locally, the
X-MS-CLIENT-PRINCIPALheader is not injected by App Service. Test by manually passing a base64-encoded principal in the header.
Expected Output¶
# Encode a test principal (matches real Azure App Service format)
PRINCIPAL=$(echo -n '{"auth_typ":"aad","name_typ":"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name","role_typ":"http://schemas.microsoft.com/ws/2008/06/identity/claims/role","claims":[{"typ":"name","val":"Alice"},{"typ":"roles","val":"admin"},{"typ":"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier","val":"user-1"}]}' | base64)
# Get user claims
curl -s "http://localhost:7071/api/auth/me" \
-H "X-MS-CLIENT-PRINCIPAL: $PRINCIPAL" | python -m json.tool
{
"identity_provider": "aad",
"user_id": "user-1",
"claims": {"name": "Alice", "roles": "admin"},
"roles": ["admin"]
}
# Access admin endpoint (requires admin role)
curl -s "http://localhost:7071/api/auth/admin" \
-H "X-MS-CLIENT-PRINCIPAL: $PRINCIPAL" | python -m json.tool
Production Considerations¶
- Scaling: EasyAuth header decoding is CPU-trivial; this pattern adds no scaling concerns.
- Security: Never trust
X-MS-CLIENT-PRINCIPALfrom external sources. The header is only trustworthy when injected by App Service itself. In production, ensure the Function App is not directly accessible (use App Service networking controls). - Claim types: Microsoft may use full URI claim types (e.g.
http://schemas.microsoft.com/...). Always check both short and long forms. - Identity providers: EasyAuth supports AAD, GitHub, Google, Facebook, and custom OpenID Connect. The principal format is consistent across providers, but available claims vary.
- Additional headers: App Service also injects
X-MS-CLIENT-PRINCIPAL-ID,X-MS-CLIENT-PRINCIPAL-NAME, andX-MS-CLIENT-PRINCIPAL-IDPas convenience headers for the user ID, display name, and identity provider respectively. - Observability: Log the
auth_typand user identifier claims for audit trails. Avoid logging full claim payloads in production.