> ## Documentation Index
> Fetch the complete documentation index at: https://docs-staging-quickstart-revamp.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> Learn about released versions of Triggers in the Actions Login Flow, including breaking changes and new features.

# Releases

Features and breaking changes released in versions of Triggers in the Login Flow are listed below.

## v3 (GA)

### Post-login

<Callout icon="file-lines" color="#0EA5E9" iconType="regular">
  We strongly recommend upgrading and using the latest GA version (v3) of the post-login flow for the best user experience possible.
</Callout>

#### Breaking changes

* `api.redirect.canRedirect()` marked as deprecated.
* `api.redirect.sendUserTo()` will no longer skip redirecting when in a non-interactive flow. This means that calls to `api.redirect.sendUserTo()` should first check if the redirect is needed before issuing the redirect. Information like `event.authentication.methods` can be consulted to see if a redirect was successfully completed and recorded via `api.authentication.recordMethod()`. Attempting to trigger a redirect in a non-interactive flow will correctly trigger an `interaction_required` error.

#### New features

* `event.authentication.methods` may now also contain custom methods completed by users within that session and recorded using `api.authentication.recordMethod()` from the `onContinuePostLogin` handler.
* `api.authentication.recordMethod()` is added as a way to store a record for the completion of a custom method in the user’s session. These APIs allow you to strictly require custom factors for certain scenarios. The user (on a specific device) will be required to complete the custom factor, regardless of whether or not an interactive login is happening.
  If the conditions are met for requiring the custom factor and the user’s session doesn’t have a record of its completion, the interactivity of the flow shouldn’t influence whether the factor is required or not.
  For example, if you wanted to implement a required custom factor, you would set up the following:

  1. In `onExecutePostLogin`, search for a record in the `event.authentication.methods` array with the custom method’s identifier URL. If the method is there and has a current enough timestamp, allow the login to continue. Otherwise, trigger a redirect to the URL implementing the custom factor using `api.redirect.sendUserTo()`. Custom data can be encoded into a JWT and signed using `api.redirect.encodeToken()`.
  2. When the user is redirected to `/continue`, the `onContinuePostLogin` handler will be invoked. Within that handler, validate any data coming back from the custom factor (if needed) and signal its completion by calling `api.authentication.recordMethod()`.

## v2 (GA)

### Post-login

#### Breaking changes

##### Perform side effects

In the pre-GA version of the post-login trigger, side effects were performed by returning an object from an Action. In Actions GA, an `api` object is provided to encapsulate these changes and provide better in-editor type hints and inline documentation.

**Update user user\_metadata**

Pre-GA Trigger:

```js lines
async function myFunction(event, context) {
  return {
    user: {
      userMetadata: {
        myParam: "foo"
      }
    }
  };
}
```

GA Trigger:

```js lines
async function onExecutePostLogin(event, api) {
  api.user.setUserMetadata('myParam', 'foo');
}
```

<Callout icon="file-lines" color="#0EA5E9" iconType="regular">
  You should not use this method in callbacks because invoking this method won't update metadata immediately. Instead, you can call this method several times throughout multiple Actions in the same flow (metadata set in one Action is applied to the transient object and is therefore available in subsequent Actions), and the engine will aggregate the changes and update the metadata all at once before the flow is completed.
</Callout>

**Update user app\_metadata**

Pre-GA Trigger:

```js lines
async function myFunction(event, context) {
  return {
    user: {
      appMetadata: {
        myParam: "foo"
      }
    }
  };
}
```

GA Trigger:

```js lines
async function onExecutePostLogin(event, api) {
  api.user.setAppMetadata('myParam', 'foo');
}
```

<Callout icon="file-lines" color="#0EA5E9" iconType="regular">
  You should not use this method in callbacks because invoking this method won't update metadata immediately. Instead, you can call this method several times throughout multiple Actions in the same flow (metadata set in one Action is applied to the transient object and is therefore available in subsequent Actions), and the engine will aggregate the changes and update the metadata all at once before the flow is completed.
</Callout>

**Deny a login**

Pre-GA Trigger:

```js lines
async function myFunction(event, context) {
  throw new Error("Access denied.");
}
```

GA Trigger:

```js lines
async function onExecutePostLogin(event, api) {
  api.access.deny("Access denied.");
}
```

<Callout icon="file-lines" color="#0EA5E9" iconType="regular">
  Throwing an error will also deny a login, but calling `api.access.deny` is the preferred approach.
</Callout>

**Add Custom Claims to the <Tooltip tip="Access Token: Authorization credential, in the form of an opaque string or JWT, used to access an API." cta="View Glossary" href="/docs/glossary?term=Access+Token">Access Token</Tooltip>**

Pre-GA Trigger:

```js lines
async function myFunc(event, context) {
  return {
    accessToken: {
      customClaims: {
        'https://example.com/custom/claim': 'Custom claim value',
      }
    }
  };
}
```

GA Trigger:

```js lines
async function myFunc(event, api) {
  api.accessToken.setCustomClaim('https://example.com/custom/claim', 'Custom claim value');
}
```

**Add Custom Claims to the <Tooltip tip="ID Token: Credential meant for the client itself, rather than for accessing a resource." cta="View Glossary" href="/docs/glossary?term=ID+Token">ID Token</Tooltip>**

Pre-GA Trigger:

```js lines
async function myFunc(event, context) {
  return {
    idToken: {
      customClaims: {
        'https://example.com/custom/claim': 'Custom claim value',
      }
    }
  };
}
```

GA Trigger:

```js lines
async function myFunc(event, api) {
  api.idToken.setCustomClaim('https://example.com/custom/claim', 'Custom claim value');
}
```

**Dynamically enable <Tooltip tip="Multi-factor authentication (MFA): User authentication process that uses a factor in addition to username and password such as a code via SMS." cta="View Glossary" href="/docs/glossary?term=multi-factor+authentication">multi-factor authentication</Tooltip>**

Pre-GA Trigger:

```js lines
async function myFunction(event, context) {
  return {
    command: {
      type: "multifactor",
      provider: "any"
    }
  };
}
```

GA Trigger:

```js lines
async function onExecutePostLogin(event, api) {
  api.multifactor.enable("duo");
}
```

**Redirect the user**

Pre-GA Trigger:

```js lines
async function myFunction(event, context) {
  return {
    command: {
      type: "redirect",
      url: "https://my-app.example.com"
    }
  };
}
```

GA Trigger:

```js lines
async function onExecutePostLogin(event, api) {
  api.redirect.sendUserTo("https://my-app.example.com");
}
```

To ensure parameters are being sent securely and to avoid replay attacks, passing data via redirects has changed significantly in Actions GA. For more information, see [Redirect with Actions](/docs/customize/actions/explore-triggers/signup-and-login-triggers/login-trigger/redirect-with-actions).

##### Manipulate scopes

Although we experimented with providing direct manipulation of ID and Access Token scopes during the Actions Beta, we do not support this functionality in Actions GA.
