|
| 1 | +--- |
| 2 | +id: configure-and-test-actions |
| 3 | +title: Configure and test Actions |
| 4 | +sidebar_label: Configure and test Actions |
| 5 | +sidebar_position: 2 |
| 6 | +--- |
| 7 | + |
| 8 | +# Configure and test Actions |
| 9 | + |
| 10 | +## Create an Action \{#create-an-action} |
| 11 | + |
| 12 | +1. Navigate to <CloudLink to="/actions">Console > Actions</CloudLink>. |
| 13 | +2. Select **Post first-factor verification** or **Post sign-in**. |
| 14 | +3. Implement the `runAction` function in the script editor. |
| 15 | +4. Under **Data source**, review the event and result types, configure environment variables, and find an example for fetching external data. |
| 16 | +5. Under **Test context**, adjust the sample event and run the script. |
| 17 | +6. Under **Settings**, enable the Action and choose its script-error behavior. |
| 18 | +7. Save the Action. |
| 19 | + |
| 20 | +Only a saved and enabled Action runs in production. |
| 21 | + |
| 22 | +## Implement `runAction` \{#implement-runaction} |
| 23 | + |
| 24 | +Keep the entry function name as `runAction`. It receives a single payload object: |
| 25 | + |
| 26 | +```js |
| 27 | +const runAction = async ({ event, environmentVariables = {} }) => { |
| 28 | + return; |
| 29 | +}; |
| 30 | +``` |
| 31 | + |
| 32 | +To decline or continue without a user update, return the no-op value supported by the specific action type. |
| 33 | + |
| 34 | +### Fetch external data \{#fetch-external-data} |
| 35 | + |
| 36 | +Use the injected `fetch` function to call an external API. For example, a Post sign-in Action can fetch a user profile: |
| 37 | + |
| 38 | +```js |
| 39 | +const runAction = async ({ event, environmentVariables = {} }) => { |
| 40 | + const response = await fetch(environmentVariables.PROFILE_API_URL, { |
| 41 | + headers: { |
| 42 | + authorization: `Bearer ${environmentVariables.PROFILE_API_TOKEN}`, |
| 43 | + }, |
| 44 | + }); |
| 45 | + |
| 46 | + if (!response.ok) { |
| 47 | + throw new Error(`Profile API returned ${response.status}`); |
| 48 | + } |
| 49 | + |
| 50 | + const profile = await response.json(); |
| 51 | + |
| 52 | + return { |
| 53 | + action: 'updateUser', |
| 54 | + user: { |
| 55 | + name: profile.name, |
| 56 | + }, |
| 57 | + }; |
| 58 | +}; |
| 59 | +``` |
| 60 | + |
| 61 | +Actions run in the authentication request path. Keep external services fast and highly available, and assume that a user may retry the sign-in. Logto does not retry an Action or fall back from the Logto Cloud remote runner to local execution. |
| 62 | + |
| 63 | +### Use environment variables \{#use-environment-variables} |
| 64 | + |
| 65 | +Use environment variables for values that should not be hardcoded in the script, such as API URLs, tokens, and feature settings: |
| 66 | + |
| 67 | +```js |
| 68 | +const { API_URL, API_TOKEN } = environmentVariables; |
| 69 | +``` |
| 70 | + |
| 71 | +Environment variables are part of the Action configuration and are visible to administrators who can read that configuration. Restrict Action-management access and never include secrets in the returned result or an error message. |
| 72 | + |
| 73 | +## Supported user patch \{#supported-user-patch} |
| 74 | + |
| 75 | +An Action can return only these user fields: |
| 76 | + |
| 77 | +| Field | Description | |
| 78 | +| -------------- | ----------------------------------------- | |
| 79 | +| `username` | Username | |
| 80 | +| `primaryEmail` | Primary email address | |
| 81 | +| `primaryPhone` | Primary phone number | |
| 82 | +| `name` | Display name | |
| 83 | +| `avatar` | Avatar URL | |
| 84 | +| `profile` | Standard OIDC profile fields | |
| 85 | +| `customData` | Additional JSON data for your application | |
| 86 | + |
| 87 | +The corresponding patch type is: |
| 88 | + |
| 89 | +```ts |
| 90 | +type ActionUserPatch = { |
| 91 | + username?: string | null; |
| 92 | + primaryEmail?: string | null; |
| 93 | + primaryPhone?: string | null; |
| 94 | + name?: string | null; |
| 95 | + avatar?: string | null; |
| 96 | + customData?: Record<string, JsonValue>; |
| 97 | + profile?: { |
| 98 | + familyName?: string; |
| 99 | + givenName?: string; |
| 100 | + middleName?: string; |
| 101 | + nickname?: string; |
| 102 | + preferredUsername?: string; |
| 103 | + profile?: string; |
| 104 | + website?: string; |
| 105 | + gender?: string; |
| 106 | + birthdate?: string; |
| 107 | + zoneinfo?: string; |
| 108 | + locale?: string; |
| 109 | + address?: { |
| 110 | + formatted?: string; |
| 111 | + streetAddress?: string; |
| 112 | + locality?: string; |
| 113 | + region?: string; |
| 114 | + postalCode?: string; |
| 115 | + country?: string; |
| 116 | + }; |
| 117 | + }; |
| 118 | +}; |
| 119 | +``` |
| 120 | + |
| 121 | +Fields such as user ID, suspension state, identities, roles, organizations, MFA configuration, password hashes, and other internal fields are rejected. |
| 122 | + |
| 123 | +For updates, `profile` and `customData` are shallow-merged with the existing objects. Returning a nested object with an existing top-level key replaces the value at that key; it is not a deep merge. Identifier updates must also pass Logto's uniqueness checks. |
| 124 | + |
| 125 | +## Test context and dry runs \{#test-context-and-dry-runs} |
| 126 | + |
| 127 | +The **Test context** is sample JSON used only when you click **Run test**. It is saved with the Action for future tests, but production executions always use the real authentication event. |
| 128 | + |
| 129 | +A dry run: |
| 130 | + |
| 131 | +- Uses the current unsaved script, sample event, and environment variables. |
| 132 | +- Executes the script and displays its raw return value. |
| 133 | +- Does not save the Action or create or update a Logto user. |
| 134 | +- Does not emit a production Action audit event or execution metric. |
| 135 | +- Does not apply the production event and result validation for the selected action type. |
| 136 | + |
| 137 | +:::caution |
| 138 | +A successful dry run proves that the script executed, but not that its result will be accepted during a real authentication flow. Test the complete flow in a non-production tenant before enabling the Action in production. |
| 139 | + |
| 140 | +Successful test results are displayed as returned. Never return passwords, environment variables, API tokens, or other secrets from a script. |
| 141 | +::: |
| 142 | + |
| 143 | +## Handle errors \{#handle-errors} |
| 144 | + |
| 145 | +The **On script error** setting applies to execution failures such as a thrown exception, a rejected promise, a failed external request, or a runner failure. It does not make an invalid result valid. |
| 146 | + |
| 147 | +| Action type | `block` (default) | `allow` | |
| 148 | +| ------------------------------ | ------------------------------------- | ---------------------------------------------------------------------------------------------------------- | |
| 149 | +| Post first-factor verification | Reject the invalid local credentials. | Not available in Console. Even if set through the API, an execution failure still rejects the credentials. | |
| 150 | +| Post sign-in | Fail the sign-in. | Continue the sign-in without applying the Action update. | |
| 151 | + |
| 152 | +For Post sign-in, a malformed or unsupported return value always fails the sign-in, even when `allow` is selected. Validate every success path in your script and return an explicit supported result or no-op. |
| 153 | + |
| 154 | +## Monitor executions \{#monitor-executions} |
| 155 | + |
| 156 | +Production executions create independent [audit log](/developers/audit-logs) events: |
| 157 | + |
| 158 | +- `Action.PostFirstFactorVerification` |
| 159 | +- `Action.PostSignIn` |
| 160 | + |
| 161 | +The audit record includes safe execution metadata such as the Action type, runtime location, duration, decision, and error-policy outcome. Passwords, environment-variable values, script source, and other sensitive values are redacted. |
| 162 | + |
| 163 | +## Configure Actions with the Management API \{#configure-actions-with-the-management-api} |
| 164 | + |
| 165 | +You can also manage Actions through the [Logto Management API](/integrate-logto/interact-with-management-api): |
| 166 | + |
| 167 | +| Method | Endpoint | Purpose | |
| 168 | +| -------- | ----------------------------------- | ------------------------------------ | |
| 169 | +| `GET` | `/api/configs/actions` | List configured Actions | |
| 170 | +| `GET` | `/api/configs/actions/{actionType}` | Get one Action | |
| 171 | +| `PUT` | `/api/configs/actions/{actionType}` | Create or replace an Action | |
| 172 | +| `PATCH` | `/api/configs/actions/{actionType}` | Partially update an Action | |
| 173 | +| `DELETE` | `/api/configs/actions/{actionType}` | Delete an Action | |
| 174 | +| `POST` | `/api/configs/actions/test` | Dry-run a script with a sample event | |
| 175 | + |
| 176 | +The action type values retain their original identifiers for backward compatibility: |
| 177 | + |
| 178 | +- `inlineHook.postFirstFactorVerification` |
| 179 | +- `inlineHook.postSignIn` |
| 180 | + |
| 181 | +An Action configuration has this shape: |
| 182 | + |
| 183 | +```ts |
| 184 | +type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue }; |
| 185 | + |
| 186 | +type ActionConfig = { |
| 187 | + script: string; |
| 188 | + environmentVariables?: Record<string, string>; |
| 189 | + contextSample?: JsonValue; |
| 190 | + enabled?: boolean; |
| 191 | + onExecutionError?: 'block' | 'allow'; |
| 192 | +}; |
| 193 | +``` |
| 194 | + |
| 195 | +When using the API, set `enabled: true` explicitly to run the Action. If `onExecutionError` is omitted, it defaults to `block`. |
0 commit comments