UserInfo endpoint
What the UserInfo endpoint returns
The UserInfo endpoint returns claims about the authenticated user as a JSON object, fetched with a valid access token.
It is defined in OIDC Core 1.0 §5.3 (opens in a new tab) and is the standard mechanism for fetching fresh profile data after the initial sign-in.
Endpoint:
https://login.luxid.lu/mga/sps/oauth/oauth20/userinfo
Both GET and POST are supported. The access token obtained from the Token endpoint must be supplied as a Bearer token.
When to call UserInfo vs read the ID Token
The ID token and the UserInfo endpoint both carry user claims, but they serve different purposes:
| ID token | UserInfo | |
|---|---|---|
| Data freshness | Snapshot at authentication time | Most current data at call time |
| Transport | Encoded in the token response | Separate HTTP call |
| Verification | Validate signature with JWKS | Protected by the Bearer token |
| Typical use | Session establishment, SSO | Display profile, pre-fill forms |
Use the ID token claims to establish the user's session and for security decisions (subject, assurance level, authentication time). Call UserInfo when you need the most current version of mutable data such as email address, phone number, or display name - particularly if the user may have changed these since they last authenticated.
See Tokens and claims for the full claims reference.
Request
GET request (recommended)
GET /mga/sps/oauth/oauth20/userinfo HTTP/1.1
Host: login.luxid.lu
Authorization: Bearer <access_token>
Accept: application/json
POST request
POST /mga/sps/oauth/oauth20/userinfo HTTP/1.1
Host: login.luxid.lu
Content-Type: application/x-www-form-urlencoded
access_token=<access_token>
The GET form is preferred - the POST form is provided for environments where adding a request header is inconvenient (for example, some script-based tooling).
cURL examples
GET
curl -s \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Accept: application/json" \
https://login.luxid.lu/mga/sps/oauth/oauth20/userinfo
POST
curl -s \
-X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "access_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \
https://login.luxid.lu/mga/sps/oauth/oauth20/userinfo
Response
The response is a JSON object. The claims returned are those in your Application's Claim Template on the LuxID side, subject to user consent. The scope parameter you sent at authorisation does not drive what is in the response - see Tokens and Claims - Requesting claims for the full claim model. The examples below illustrate typical Claim Template configurations.
Minimal response (Claim Template: openid only)
{
"sub": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
The sub claim is always present. It is the stable, opaque LuxID user identifier for your application. Do not display it to users - it is a technical identifier only.
Standard profile response (Claim Template includes name, email, phone)
{
"sub": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Jean Dupont",
"given_name": "Jean",
"family_name": "Dupont",
"email": "jean.dupont@example.lu",
"email_verified": true,
"phone_number": "+352691123456",
"phone_number_verified": true,
"updated_at": 1716374400
}
Extended response with assurance claims
{
"sub": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Jean Dupont",
"given_name": "Jean",
"family_name": "Dupont",
"email": "jean.dupont@example.lu",
"email_verified": true,
"phone_number": "+352691123456",
"phone_number_verified": true,
"updated_at": 1716374400,
"acr": "urn:luxid:acr:level:substantial",
"amr": ["pwd", "otp"],
"birthdate": "1985-03-14",
"address": {
"street_address": "42 Rue de la Liberté",
"locality": "Luxembourg",
"postal_code": "L-1930",
"country": "LU"
}
}
Claim descriptions
The OIDC standard groups claims under conceptual scopes (openid, profile, email, phone, address). LuxID lists them here for reference, but scope is not what gates release in LuxID - your Claim Template is. The "Standard scope group" column tells you which OIDC scope the claim conceptually belongs to.
| Claim | Type | Standard scope group | Description |
|---|---|---|---|
sub | string | openid | Stable pairwise or public subject identifier |
name | string | profile | Full display name |
given_name | string | profile | First name(s) |
family_name | string | profile | Family name |
locale | string | profile | BCP 47 language tag (fr, de, lb, en) |
updated_at | integer | profile | Unix timestamp of last profile update |
email | string | email | Primary email address |
email_verified | boolean | email | Whether LuxID has verified the email |
phone_number | string | phone | E.164 phone number |
phone_number_verified | boolean | phone | Whether LuxID has verified the number |
birthdate | string | profile | ISO 8601 date of birth |
address | object | address | OIDC address claim (structured) |
acr | string | openid | Authentication Context Class Reference (assurance level) |
amr | array | openid | Authentication Methods References |
Claims that are not in your Application's Claim Template are omitted from the response - they are not returned as null or empty strings. To add a claim, request a Claim Template update from LuxID.
How long to cache UserInfo responses
Do not cache UserInfo responses for longer than the access token lifetime. The access token's exp claim (or the expires_in value from the token response) defines the upper bound.
Practical recommendations:
- Short-lived sessions (under 5 minutes): skip caching - call UserInfo once per session.
- Medium sessions (5-60 minutes): cache the response keyed by the access token, with a TTL equal to
expires_inminus a small safety margin (for example, 30 seconds). - Long-lived sessions: do not cache at all if you need to display current email or phone - call UserInfo on each page load or on a short timer. Email and phone number can change.
The updated_at claim can help you decide whether to refresh: if updated_at is newer than your cached timestamp, discard the cache and re-fetch.
Claim availability
A claim is only returned if all of the following hold:
- The claim is in your Application's Claim Template on the LuxID side. The
scopeparameter you sent at authorisation does not gate claim release - adding a scope does not unlock a claim; only a Claim Template update does. See Tokens and Claims - Requesting claims. - The user consented to sharing that claim with your application.
- The claim has a value in the user's LuxID profile (for example,
addressis only returned if the user has added an address - LuxID uses a progressive profile, so optional attributes are often absent).
Design your application to handle absent optional claims gracefully: an unprovided or non-consented claim is omitted entirely, not returned as null or an empty string. If a claim you expected is missing and it is not a consent or profile-value issue, the fix is a Claim Template update - contact LuxID.
Error responses
401 unauthorized - invalid or expired Access Token
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token",
error_description="The access token is expired, revoked, or malformed"
{
"error": "invalid_token",
"error_description": "The access token is expired, revoked, or malformed"
}
Resolution: obtain a new access token via the Token endpoint using a refresh token, or re-initiate the authorisation flow. See Session management for silent renewal patterns.
401 unauthorized - missing bearer token
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="luxid"
Resolution: include the Authorization: Bearer <access_token> header.
403 forbidden - insufficient scope
HTTP/1.1 403 Forbidden
{
"error": "insufficient_scope",
"error_description": "The access token does not have the required scope to call UserInfo"
}
Resolution: ensure the access token was issued with at least the openid scope. Note that adding profile/email/phone scopes does not unlock additional claims at UserInfo - claim release is governed by your Claim Template, not the scope parameter. If a claim you need is missing, request a Claim Template update (see Claim availability above). For a partner-specific technical claim (luxid_<partner-prefix>_<name>), a Claim Template update is only half the fix: you must also set a value for the user via the Partner API claims capability - a declared-but-never-set technical claim is omitted.
404 not found
Returned if the user account has been deleted after the access token was issued.
Sequence diagram
Integration checklist
- Request only the scopes your application actually needs - do not request
profile email phone addressif you only needsub. - Store
subas the user's stable identifier in your database - do not store or compare email addresses as identifiers. - Handle absent claims:
email,phone_number,birthdate, andaddressmay all be missing. - Cache responses responsibly: key by access token, TTL equal to
expires_in. - Refresh on
401: implement silent token renewal before calling UserInfo again. - Verify that
email_verifiedistruebefore treating an email address as confirmed.
Related pages
- Tokens and claims - full claims catalogue
- OpenID Connect - obtaining an access token
- Token introspection - validating tokens server-side
- Session management - silent renewal patterns
- OAuth and OIDC error codes - error reference