# 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.