# Custom claims via post-login Action

## 1. Write the Action

Dashboard > Actions > Library > Build Custom > post-login trigger:

```
exports.onExecutePostLogin = async (event, api) => {
  const namespace = "https://YOUR-DOMAIN";
  if (event.authorization) {
    api.idToken.setCustomClaim(namespace + "/roles", event.authorization.roles);
    api.accessToken.setCustomClaim(namespace + "/roles", event.authorization.roles);
  }
};
```

Rules that matter:

- Namespaced names only. Auth0 drops non-namespaced custom claims silently. `https://YOUR-DOMAIN/roles` works; `roles` does not.
- The event object is read-only. Assigning to `event.user` does nothing; all writes go through `api`.
- idToken claims and accessToken claims are separate calls. Setting one does not set the other.

## 2. Secrets and dependencies

Need an API key inside the Action? Add it under the Action's Secrets (never hardcode). Need an npm package? Add it in the Action's dependency manager.

## 3. Deploy AND attach

This is the step everyone misses: clicking Deploy is not enough. Go to Actions > Flows > Login, drag the Action into the flow, and Apply. The flow order is the execution order.

## 4. Test

Use the flow's test runner first, then do a real login in an incognito window. Decode the fresh token and confirm the claim. Old tokens never gain new claims.

## Common extras

- Deny bad actors: `api.access.deny("end_users_not_allowed")` with a reason string.
- MFA gating: `api.multifactor.enable("any")` only when `event.user.multifactor` is empty, or you loop enrolled users.
- Redirects: `api.redirect.sendUserTo()` needs a resume handler; do not redirect unconditionally.

## Checklist

- Namespaced claim names, both token targets set deliberately.
- Deployed, attached to the flow, verified with a fresh token.