Files
zitadel/apps/docs/content/apis/actions/external-authentication.mdx
T
2bd42e8fc4 fix(actions): allow adding raw metadata values via appendMetadataRaw (#12567)
# Which Problems Are Solved

Since #10666 (v4.1+, backported to v4.x), metadata values set through
actions v1 (`api.metadata.push` and `api.v1.user.appendMetadata`) are
always JSON-encoded via `json.Marshal`. This made the write path
consistent with the JSON-based read path, but removed the ability to
store raw (unencoded) metadata values:

- A scalar string is now always stored quoted (`"de"` instead of `de`).
- The previous byte-array convention (mapping a string to an integer
array in the script, handled by `mapBytesToByteArray` introduced in
#5526) now stores the literal integer-array text (e.g. `[100,101]`)
instead of the raw bytes.

Customers migrating from v3.x whose downstream systems base64-decode
metadata values from tokens and expect raw bytes have no way to produce
them anymore — changing the consuming system is not always possible.

Reverting the default is not an option either, as clients that adopted
actions v1 on v4.x now rely on the JSON encoding.

# How the Problems Are Solved

- Adds a new, opt-in function `api.v1.user.appendMetadataRaw(key,
value)` to the actions v1 login flows (external / internal
authentication post authentication and pre creation), next to the
existing `appendMetadata`.
- The value is stored as raw bytes without JSON encoding:
  - a string is stored as its plain UTF-8 bytes (`de`, not `"de"`)
- byte arrays (`Uint8Array` or a plain array of integers 0-255, the old
convention) are stored as-is, so existing v3 scripts using a
string-to-byte-array helper only need to switch the function name
  - other types (and empty values) throw an error
- The existing `appendMetadata` and `api.metadata.push` behavior remains
byte-for-byte unchanged.

# Additional Changes

- Documented `appendMetadataRaw` (and the JSON encoding behavior of
`appendMetadata`) in the external and internal authentication actions
docs.
- Added unit tests for the new function (through a real goja runtime)
and a test locking in the existing `appendMetadata` JSON-encoding
behavior.

# Additional Context

- Regression introduced as a side effect of #10666 (which fixed #10470);
the raw byte handling was originally introduced in #5526.
- Reported by a customer upgrading from v3.4.x to v4.16.x, whose
PostAuthentication action maps token payload claims into user metadata.
- Requires backport to v4.x.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 <[email protected]>
2026-08-11 15:44:15 +02:00

149 lines
6.5 KiB
Plaintext

---
title: External Authentication Flow
description: "Trigger custom JavaScript logic in ZITADEL Actions during the authentication flow for external identity providers."
---
This flow is executed if the user logs in using an [identity provider](/guides/integrate/identity-providers/introduction).
The flow is represented by the following Ids in the API: `FLOW_TYPE_EXTERNAL_AUTHENTICATION` and `1`
## Post Authentication
A user has authenticated externally. ZITADEL retrieved and mapped the external information.
The trigger is represented by the following Ids in the API: `TRIGGER_TYPE_POST_AUTHENTICATION` or `1`.
### Parameters of Post Authentication Action
- `ctx`
The first parameter contains the following fields
- `accessToken` *string*
The access token returned by the identity provider. This can be an opaque token or a JWT
- `refreshToken` *string*
The refresh token returned by the identity provider if there is one. This is most likely to be an opaque token.
- `claimsJSON()` [*idTokenClaims*](../openidoauth/claims)
Returns all claims of the id token
- `getClaim(key)` *Any*
Returns the requested [id token claim](../openidoauth/claims)
- `idToken` *string*
The id token provided by the identity provider.
- `v1`
- `externalUser` [*externalUser*](./objects#external-user)
- `authError` *string*
This is a verification errors string representation. If the verification succeeds, this is "none"
- `authRequest` [*auth request*](/apis/actions/objects#auth-request)
- `httpRequest` [*http request*](/apis/actions/objects#http-request)
- `providerInfo` *Any*
Returns the response of the provider. In case the provider is a Generic OAuth Provider, the information is accessible through:
- `rawInfo` *Any*
- `org`
- `getMetadata()` [*metadataResult*](./objects#metadata-result)
- `api`
The second parameter contains the following fields
- `v1`
- `user`
- `appendMetadata(string, Any)`
The first parameter represents the key and the second a value which will be stored.
The value is JSON-encoded, so a string is stored with surrounding quotes (e.g. `"de"`).
- `appendMetadataRaw(string, string | Uint8Array | number[])`
The first parameter represents the key and the second a value which will be stored as raw bytes without JSON encoding.
A string is stored as its plain UTF-8 bytes (e.g. `de` without quotes), a byte array (`Uint8Array` or an array of integers between 0 and 255) is stored as-is.
Use this if the systems consuming the metadata expect an unencoded value.
- `setFirstName(string)`
Sets the first name
- `setLastName(string)`
Sets the last name
- `setNickName(string)`
Sets the nickname
- `setDisplayName(string)`
Sets the display name
- `setPreferredLanguage(string)`
Sets the preferred language. Please use the format defined in [RFC 5646](https://www.rfc-editor.org/rfc/rfc5646)
- `setPreferredUsername(string)`
Sets the preferred username
- `setEmail(string)`
Sets the email address of the user
- `setEmailVerified(boolean)`
Sets the email address verified or unverified
- `setPhone(string)`
Sets the phone number of the user
- `setPhoneVerified(boolean)`
Sets the phone number verified or unverified
- `metadata`
Array of [*metadata*](./objects#metadata-with-value-as-bytes). This function is deprecated, please use `api.v1.user.appendMetadata`
## Pre Creation
A user selected **Register** on the overview page after external authentication. ZITADEL did not create the user yet.
The trigger is represented by the following Ids in the API: `TRIGGER_TYPE_PRE_CREATION` or `2`.
### Parameters of Pre Creation
- `ctx`
The first parameter contains the following fields
- `v1`
- `user` [*(human)*](./objects#human-user)
- `authRequest` [*auth request*](/apis/actions/objects#auth-request)
- `httpRequest` [*http request*](/apis/actions/objects#http-request)
- `org`
- `getMetadata()` [*metadataResult*](./objects#metadata-result)
- `api`
The second parameter contains the following fields
- `metadata`
Array of [*metadata*](./objects#metadata-with-value-as-bytes). This function is deprecated, please use `api.v1.user.appendMetadata`
- `setFirstName(string)`
Sets the first name
- `setLastName(string)`
Sets the last name
- `setNickName(string)`
Sets the nickname
- `setDisplayName(string)`
Sets the display name
- `setPreferredLanguage(string)`
Sets the preferred language, the string has to be a valid language tag as defined in [RFC 5646](https://www.rfc-editor.org/rfc/rfc5646)
- `setGender(int)`
Sets the gender.
<ul><li>0: unspecified</li><li>1: female</li><li>2: male</li><li>3: diverse</li></ul>
- `setUsername(string)`
Sets the username
- `setEmail(string)`
Sets the email
- `setEmailVerified(bool)`
If true the email set is verified without user interaction
- `setPhone(string)`
Sets the phone number
- `setPhoneVerified(bool)`
If true the phone number set is verified without user interaction
- `v1`
- `user`
- `appendMetadata(string, Any)`
The first parameter represents the key and the second a value which will be stored.
The value is JSON-encoded, so a string is stored with surrounding quotes (e.g. `"de"`).
- `appendMetadataRaw(string, string | Uint8Array | number[])`
The first parameter represents the key and the second a value which will be stored as raw bytes without JSON encoding.
A string is stored as its plain UTF-8 bytes (e.g. `de` without quotes), a byte array (`Uint8Array` or an array of integers between 0 and 255) is stored as-is.
Use this if the systems consuming the metadata expect an unencoded value.
## Post Creation
A user selected **Register** on the overview page after external authentication and ZITADEL successfully created the user.
The trigger is represented by the following Ids in the API: `TRIGGER_TYPE_POST_CREATION` or `3`.
### Parameters of Post Creation
- `ctx`
The first parameter contains the following fields
- `v1`
- `getUser()` [*user*](./objects#user)
- `authRequest` [*auth request*](/apis/actions/objects#auth-request)
- `httpRequest` [*http request*](/apis/actions/objects#http-request)
- `org`
- `getMetadata()` [*metadataResult*](./objects#metadata-result)
- `api`
The second parameter contains the following fields
- `userGrants` Array of [*userGrant*](./objects#user-grant)'s
- `v1`
- `appendUserGrant(`[`userGrant`](./objects#user-grant)`)`