Skip to content
  • There are no suggestions because the search field is empty.

Extend Authorization in ZITADEL with Organization Metadata + preAccessToken Action

Map roles to permissions in your org metadata and append a permissions claim to Access Tokens during the pre-issuance flow.

This guide shows how to enrich Access Tokens with a permissions claim derived from your Organization’s role-to-permissions mapping. You’ll store that mapping in Organization metadata, verify ZITADEL’s webhook signature, read the metadata inside a Function → preAccessToken Action, and return append_claims so the permissions array is issued with every token. We include example metadata shapes, sample responses, and UI/API steps.


Reference implementation: Check our Actions V2 + Cloudflare Workers utility and read the AUTHORIZATION.md guide. It exposes an endpoint to handle role-to-permissions mapping.

Why this pattern?

Many apps authorize on permissions, while ZITADEL natively grants roles on projects/grants. By adding a lightweight Action in the pre access token creation flow, you can translate roles → permissions at token time and deliver a standard permissions claim your API gateways and services already understand. This runs just before token issuance in the PreAccessToken Flow.

Prerequisites

  • ZITADEL v3+ with Actions v2 enabled on your instance.

  • A service user (PAT or Client Credentials) with access to Organization Service v2 to read metadata. (See API reference).

  • A project with users who already hold project roles via user grants.


Step 1 — Model your permissions in Organization Metadata

You’ll store a mapping from role namearray of permissions in Organization metadata. ZITADEL’s v2 Organization API exposes ListOrganizationMetadata / SetOrganizationMetadata.

Recommended shape: Per-role keys (simple & scalable)

Create one metadata entry per role, where the key equals the role name:

  • Key: admin → Value (JSON string): ["read:all","write:all","delete:all"]

  • Key: editor → ["posts:read","posts:write"]

  • Key: viewer → ["posts:read"]

This keeps updates surgical (change one role value, no collisions) and matches the logic “for each user role, look up a same-named metadata key”.

Where to set it
  • Console: Organization → Metadata (paste JSON strings in the Value field).

IMG_4440

Step 2 — Create a Target (webhook) and capture the signing key

Create a Target that ZITADEL will call from the Action. The response includes a signingKey that you must use to verify the request signature. Example (docs snippet):

curl -L -X POST "https://$CUSTOM_DOMAIN/v2/actions/targets" \

  -H "Authorization: Bearer <TOKEN>" -H "Content-Type: application/json" \

  --data-raw '{

    "name": "Authorization Webhook",

    "restCall": { "interruptOnError": false },

    "endpoint": "https://<HOSTING_DOMAIN>/",

    "timeout": "10s"

  }'

# Response (excerpt)

# { "id": "342320645008366333", "signingKey": "<SIGNING_KEY>" }

 

Keep that signingKey safe; this must be saved as the AUTHORIZATION_SIGNING_KEY environment variable. See the Actions V2 + Cloudflare Workers utility and read the AUTHORIZATION.md guide for more details.


Step 3 — Implement the preAccessToken Action (Function)

This Action runs in the Function execution → preAccessToken condition and can append claims by returning an append_claims array. 

What the sample code will do
  1. Verify signature using the signingKey.

  2. Parse request and collect the user’s roles (e.g., from user_grants[].roles) supplied in the preAccessToken payload.

  3. Fetch Organization metadata via ListOrganizationMetadata (v2).

  4. Map roles → permissions by reading a metadata entry whose key equals the role name.

  5. Return an append_claims object with a deduplicated permissions array.

Minimal example response
{

  "append_claims": [

    {

      "key": "permissions",

      "value": ["read:all", "write:all"]

    }

  ]

}

 

That’s all that’s required for ZITADEL to add permissions into the Access Token. 

Step 4 — Wire it up in Console

  1. Go to Console → Actions.

  2. Create Function → select pre access token creation.

  3. Choose your Authorization Webhook Target and save.

Step 5 — Test and inspect the token

Use any OIDC client (or ZITADEL’s OIDC Playground) to request an Access Token and then decode the JWT to verify the claim. A minimal vanilla JS app can be used to login and decode the user tokens to demonstrate the final result:

Example decoded Access Token payload (excerpt)
{

  "iss": "https://auth.example.com",

  "aud": [

    "259256588127174658",

    "257786991247294468"

  ],

  "sub": "259242039378444290",

  "permissions": ["read:all", "write:all", "delete:all"],

  "exp": 1762730441,

  "iat": 1762726841

}