Users
Users are the individuals who authenticate through AuthHero. Each user belongs to one or more tenants and can have different attributes and permissions.
User Properties
Each user has:
- user_id: Unique identifier (format:
{connection}|{id}) - email: Email address
- email_verified: Whether the email has been verified
- name: Full name
- nickname: Display name
- picture: Profile picture URL
- user_metadata: Custom data editable by the user
- app_metadata: Custom data editable by administrators only
- identities: Array of linked authentication identities
- blocked: Auth0-parity account block flag (see Blocking a User)
- login_count / last_login / last_ip: Login activity counters (see Login Activity)
Blocking a User
Setting blocked: true disables an account without deleting it:
PATCH /api/v2/users/{user_id}
{
"blocked": true
}Effects:
- All authentication is refused — password, passwordless/OTP, social and enterprise logins, silent auth, and token exchange fail with
403 access_denied("User is blocked"). Password logins are checked after credential validation, so the block status is never leaked to someone without valid credentials. - Refresh tokens stop working — the
refresh_tokengrant returnsinvalid_grantwith "User is blocked". - Existing sessions are revoked — on the transition into blocked, every session and its refresh tokens are revoked and a
SUCCESS_REVOCATIONlog event is written. Already-issued access tokens are stateless JWTs and remain valid until they expire.
Blocking always operates on the primary account: blocking it blocks every linked identity, and a blocked value sent in an update targeting a linked identity is applied to the primary so it can't silently do nothing.
Unblock with blocked: false (this revokes nothing). You can filter on the flag with GET /api/v2/users?q=blocked:true.
SCIM inbound provisioning maps active: false to blocked: true (and vice versa), with the same session revocation on deactivation. Note that the admin UI has no blocked toggle yet — blocking is Management API / SCIM only today.
Login Activity
login_count, last_login, and last_ip appear on user records returned by the Management API and can be used in q= filters and sorts (e.g. q=login_count:0 finds users who never logged in). They are stored in a separate write-often user_activity table (see the database schema) — joined into user reads transparently — so the profile row isn't rewritten on every login.
The same table backs the failed-password lockout window (failed_logins) and last_password_reset; those fields are internal to the authentication flows and are not exposed through the Management API.
Account Linking
Account linking allows a single user to have multiple authentication identities (connections) consolidated into one user profile. This is useful when:
- A user signs up with email/password and later wants to link a social login
- A user has multiple email addresses they want to use with the same account
- You want to consolidate user accounts that represent the same person
Primary and Secondary Accounts
When accounts are linked:
- One account becomes the primary account - this is the main user profile
- Other accounts become secondary (linked) accounts - these are attached as additional identities
Updating Linked Accounts
You can update properties of linked accounts by specifying the connection parameter in the user update API:
PATCH /api/v2/users/{primary_user_id}
{
"phone_number": "+1234567890",
"connection": "sms"
}Supported operations on linked accounts:
- Update user metadata and app metadata
- Update email verification status
- Update phone numbers (for SMS connections)
- Update passwords (for Username-Password-Authentication connections only)
Important Limitations
- You cannot directly update a linked (secondary) account - all updates must go through the primary account
- Password updates on linked accounts are only supported for
Username-Password-Authenticationconnections - Attempting to update a linked account directly (via its own user_id) will return a 404 error
Querying Linked Accounts
When retrieving a primary user, all linked identities are included in the identities array:
{
"user_id": "email|primary-user",
"email": "[email protected]",
"identities": [
{
"provider": "email",
"user_id": "primary-user",
"connection": "email",
"isSocial": false
},
{
"provider": "sms",
"user_id": "secondary-user",
"connection": "sms",
"isSocial": false,
"profileData": {
"phone_number": "+1234567890"
}
}
]
}OAuth Grants
When a user authorizes a third-party client, AuthHero stores the granted scopes in a grants row keyed by (tenant_id, user_id, client_id, audience). Re-authorizing the same client/audience unions the new scopes into the existing row, so the screen only re-appears when a client asks for something genuinely new.
First-party clients (the default, is_first_party: true) and the OIDC basic scopes (openid, profile, email) never produce a grant — they're exempt from the gate.
Schema (Auth0 wire shape)
| Field | Type | Notes |
|---|---|---|
id | string | nanoid |
user_id | string | references the granting user |
clientID | string | references the client (Auth0 camelCase oddity) |
audience | string? | target audience the grant applies to |
scope | string[] | unioned set of granted OAuth scopes |
List grants
GET /api/v2/grants?user_id={user_id}
Authorization: Bearer <token with read:grants or auth:read>Supports user_id, client_id, and audience query filters plus include_totals for the paginated envelope.
When the adapter doesn't implement grants, the endpoint returns an empty array — the feature degrades to "all clients behave as first-party" rather than failing.
Delete grants
DELETE /api/v2/grants/{id}
DELETE /api/v2/grants?user_id={user_id}