UserInfo Endpoint
The UserInfo endpoint returns profile information about the authenticated user or agent. It is the standard OpenID Connect endpoint for reading the current profile claims (the name/value facts about the user, such as email) associated with an access token. Call it with the access token you received from the token endpoint; terms are explained in the glossary.
GET / POST /orgs/{orgId}/api/v1/oauth/userinfo
When to Use UserInfo
The ID token contains user information captured at the moment of authentication. The UserInfo endpoint returns current profile data. Call it when you need to:
- Display a user profile page.
- Sync user data with your database.
- Check current roles (only for clients that enable the
rolesclaim). - Retrieve claims that aren't in the ID token.
Request
Include the access token in the Authorization header. A user's token must carry the openid scope, even if it has no other scope; without it the endpoint returns 403 insufficient_scope.
curl -X GET https://app.lumoauth.dev/orgs/acme-corp/api/v1/oauth/userinfo \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response: User Identity
For a user token granted openid profile email, from a client that enables the roles claim:
{
"sub": "12345",
"name": "John Smith",
"email": "john@example.com",
"email_verified": true,
"picture": "https://example.com/avatars/john.jpg",
"roles": ["ROLE_USER", "ROLE_EDITOR"],
"tenant": "acme-corp",
"updated_at": 1704063600
}
Response: Agent Identity
For AI agents authenticated via client credentials or workload federation:
{
"sub": "agent_analyst_bot",
"name": "Financial Analyst Bot",
"agent_id": "agt_abc123",
"identity_type": "agent",
"capabilities": [
"read:reports",
"tool:search",
"write:analysis"
],
"workload_identity": "aws:sts:analyst-server",
"tenant": "acme-corp"
}
Response: Client Identity
For a token issued to an OAuth client with the client credentials grant (no user, not an agent):
{
"sub": "client_abc123def456",
"client_id": "abc123def456",
"name": "Reporting service",
"tenant": "acme-corp",
"identity_type": "client"
}
Response Fields
Standard OIDC Claims
| Claim | Description | Scope Required |
|---|---|---|
sub | Unique identifier for the user or agent | openid |
name | Full name | profile |
email | Email address | email |
email_verified | Whether the email has been verified | email |
picture | Profile picture URL | profile |
given_name, family_name, preferred_username, locale, updated_at, … | Other profile claims | profile |
phone_number, phone_number_verified | Phone | phone |
address | Postal address | address |
A token with only openid gets sub and tenant, nothing else.
LumoAuth-Specific Claims
| Claim | Description |
|---|---|
roles | Array of roles assigned to the user. Returned only when the token's client explicitly enables User roles under its JWT claims; a scope cannot request it |
tenant | Organization slug the identity belongs to |
identity_type | agent or client. Absent for user tokens |
capabilities | Agent-specific: allowed actions |
workload_identity | Agent-specific: external identity source |
DPoP-bound tokens
DPoP ties a token to a key your client holds, so a stolen copy cannot be replayed from another machine. If the access token was issued with a DPoP proof (token_type: "DPoP"), present it with the DPoP scheme and a fresh proof for this exact request. The proof must carry htm: "GET", htu set to the userinfo URL, and ath (the base64url SHA-256 of the access token).
curl https://app.lumoauth.dev/orgs/acme-corp/api/v1/oauth/userinfo \
-H "Authorization: DPoP YOUR_ACCESS_TOKEN" \
-H "DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7Li4ufX0..."
Each proof is validated once per request (its jti is consumed), so a single proof is never reported as a replay against itself; sending the same proof twice is.
If the organization requires server nonces (settings.security.dpop_require_nonce), a proof without a fresh nonce is answered with:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: DPoP error="use_dpop_nonce", error_description="Resource server requires nonce in DPoP proof"
DPoP-Nonce: 5d2f0a9c8b7e6d4c3b2a1f0e9d8c7b6a
Content-Type: application/json
{"error": "use_dpop_nonce", "error_description": "Resource server requires a fresh nonce in the DPoP proof"}
Build a new proof with that value in nonce (new jti, fresh iat, same ath) and repeat the request. See Token formats.
Errors
| HTTP | error | Cause |
|---|---|---|
| 400 | invalid_request | No Authorization header, or a scheme other than Bearer or DPoP |
| 401 | invalid_token | Missing, expired, revoked, or wrong-issuer token; a DPoP-bound token presented as Bearer; or the user has since been blocked, deactivated or deleted, signed out everywhere, or had their password reset (tokens issued before that are refused at once) |
| 401 | invalid_dpop_proof | Proof malformed, expired, replayed, or its key does not match the token's cnf.jkt |
| 401 | use_dpop_nonce | The organization requires a server nonce; retry with the DPoP-Nonce value |
| 403 | insufficient_scope | The token lacks openid (a user token with no scopes at all included) |
Common mistakes
- Only
subcomes back. The token was grantedopenidbut notprofileoremail. Request those scopes; the client's JWT-claims settings cannot add claims the scopes don't cover. rolesis missing. Roles are opt-in per client. Turn on User roles under the application's JWT claims.
Related
- Token endpoint — obtain the access token needed to call UserInfo
- Scopes — which claims are returned depends on the scopes granted
- Token formats — DPoP overview
Standards used on this page
| Plain name | Standard |
|---|---|
| OpenID Connect (OIDC) | OpenID Connect Core 1.0 |
| Bearer tokens | RFC 6750 |
| Sender-constrained tokens (DPoP) | RFC 9449 |
See the standards reference for what each one covers and how LumoAuth uses it.