UserInfo
This page helps you read what the auth server knows about a signed-in user, from the UserInfo endpoint, /userinfo.
It’s the OpenID Connect endpoint for a user’s claims: your app calls it with the user’s access token, and gets back the claims of the scopes that token carries.
Read a user’s claims
Section titled “Read a user’s claims”-
Sign the user in with a scope that has
openid, plus the scopes for the claims you want, such asopenid profile email. See Authorize. -
Call
/userinfowith the access token:Terminal window curl https://auth.example.com/userinfo \-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6Ii4uLiJ9..." -
Check
subbefore you use any claim. It must be exactly thesubof the ID token you validated at sign-in. If it isn’t, discard the whole answer and use none of its claims: the answer is about another user than the one who signed in. OpenID Connect Core section 5.3.2 requires this check. -
Read the claims:
{"email_verified": true,"family_name": "Doe","given_name": "Jane","name": "Jane Doe","picture": "https://auth.example.com/userinfo/picture/2bd9ff17-7cb1-4c11-8d6d-6a3e1a0f5a6b","preferred_username": "jane","profile": "https://auth.example.com/account/profile","sub": "2bd9ff17-7cb1-4c11-8d6d-6a3e1a0f5a6b","updated_at": 1759900000}
The request
Section titled “The request”Send the access token one way:
- In the
Authorizationheader, asBearerand the token, on a GET or a POST. This is the way to use. - In a POST body, as the
access_tokenparameter, withContent-Type: application/x-www-form-urlencoded, as RFC 6750 section 2.2 defines.
A token in the query string isn’t read, since it would end up in logs and the browser’s history. Sending the token twice, in two headers, two parameters or both ways, is refused with invalid_request.
The token must be an access token the auth server issued for a user, with openid in its scope. A client credentials token never has openid, and an ID token or a refresh token isn’t accepted.
The answer
Section titled “The answer”The answer is 200 OK with the claims as a JSON object. sub, the user’s subject, is always there. The rest come from the token’s scope:
| Scope | Claims |
|---|---|
profile |
name, given_name, middle_name, family_name, nickname, preferred_username, profile, picture, website, gender, birthdate, zoneinfo, locale and updated_at |
email |
email and email_verified |
address |
address, an object with street_address, locality, region, postal_code, country and, when the country is set, formatted |
phone |
phone_number and phone_number_verified |
groups |
groups, the identifiers of the user’s groups set to be included in the ID token |
attributes |
attributes, the user’s attributes and their groups’ set to be included in the ID token, as keys and values |
A claim with no value is left out, as Scopes explains. profile is /account/profile and picture the user’s profile picture, both on the auth server’s base URL. updated_at is the last time the user’s details changed, in seconds since 1970.
The claims are read when you call, so they’re current even when the token is older than the user’s last change. The answer is plain JSON, never a signed JWT.
Errors
Section titled “Errors”An error is answered with a WWW-Authenticate: Bearer realm="goiabada" header naming the error and its error_description, and the same two in a JSON body, as RFC 6750 section 3 defines:
HTTP/1.1 401 UnauthorizedWWW-Authenticate: Bearer realm="goiabada", error="invalid_token", error_description="Session has been terminated"Content-Type: application/jsonCache-Control: no-storePragma: no-cache
{ "error": "invalid_token", "error_description": "Session has been terminated"}| Status | error |
error_description |
Why |
|---|---|---|---|
| 401 | none | none | No token was sent. The answer has only WWW-Authenticate: Bearer realm="goiabada" and no body. |
| 400 | invalid_request |
“The Authorization header must be sent once.”, “The access_token parameter must be sent once.” or “The access token must be sent by one method only.” | The token was sent twice |
| 400 | invalid_request |
“The request body could not be parsed.” | A POST body that can’t be read |
| 401 | invalid_token |
“The access token is invalid.” | The token is expired, its signature doesn’t verify, or it isn’t an access token |
| 403 | insufficient_scope |
“Insufficient scope.” | The token’s scope has no openid |
| 401 | invalid_token |
“Session has been terminated” | The user signed out, was disabled or deleted, or their credentials changed |
| 401 | invalid_token |
“Session has expired” | The session the token was issued in reached its idle timeout or maximum lifetime |
The last two make /userinfo stricter than the token’s own exp: an access token stops working here as soon as the session it came from ends. A token with no session, from an offline refresh token or the password grant, stops when the user is disabled or their credentials change. See Ending sessions.
Calling it from a browser
Section titled “Calling it from a browser”JavaScript in a browser can call /userinfo when its origin is registered as a web origin on a client. Without one, the browser blocks the call.