Scopes & Claims

Scopes control what user data your application can access. Claims are the individual pieces of data returned when you call the UserInfo endpoint or decode the ID token. Request only the scopes you need — users see what you are requesting on the consent screen.

Available scopes

Scope Description Claims included
openid Required. Identifies the user sub
profile Basic profile information preferred_username, display_name, picture, created_at
email User's email address email, email_verified
age_verification Verified age bracket age_verified, age_bracket, age_brackets_verified, verification_level, verified_at
connections Connection details for your site connection (object with connected_at, last_used_at, scopes_granted)

Scope details and examples

openid (required)

This scope is always required. It returns the user's unique Xident ID as the sub claim. This identifier is stable and will not change — use it as the primary key for your user records.

// scope: openid
// Returns the user's unique identifier
{
  "sub": "xid_abc123def456"
}

profile

Returns basic profile information. None of this is PII — usernames and display names are chosen by the user and can be changed. The picture is a Xident-hosted avatar.

// scope: openid profile
// Returns basic profile info (no PII)
{
  "sub": "xid_abc123def456",
  "preferred_username": "alex_t",
  "display_name": "Alex T.",
  "picture": "https://api.xident.io/avatars/xid_abc123def456.jpg",
  "created_at": "2026-01-10T08:00:00Z"
}

email

Returns the user's email if they have one registered and consent to sharing it. The email_verified claim tells you whether the email has been confirmed.

// scope: openid email
// Returns the user's email (if they have one and consented)
{
  "sub": "xid_abc123def456",
  "email": "alex@example.com",
  "email_verified": true
}

age_verification

This is Xident's unique differentiator. No other OAuth provider offers cryptographically verified age information. The age bracket was established through ML-based liveness detection, document verification, or both — it is not self-reported.

// scope: openid age_verification
// Returns verified age information — Xident's unique differentiator
{
  "sub": "xid_abc123def456",
  "age_verified": true,
  "age_bracket": "18+",
  "age_brackets_verified": ["12+", "15+", "18+"],
  "verification_level": "ml",
  "verified_at": "2026-02-15T10:30:00Z"
}
Claim Type Description
age_verified boolean Whether the user has completed age verification
age_bracket string The highest verified bracket: "12+", "15+", "18+", "21+", or "25+"
age_brackets_verified string[] All brackets the user is verified for (e.g., a 20-year-old verified as 18+ is also 15+ and 12+)
verification_level string How they were verified: "ml" (liveness + ML), "document" (OCR + face match), or "both"
verified_at string ISO 8601 timestamp of when the verification was completed

Privacy by design

The age_verification scope gives you a bracket (e.g., "18+"), not an exact age or birthdate. No face images, document scans, or personal information are shared. You get a verified pass/fail — nothing more.

connections

Returns metadata about the user's connection to your site. You can only see your own connection — not the user's connections to other sites.

// scope: openid connections
// Returns the user's connected sites (only your site's connection)
{
  "sub": "xid_abc123def456",
  "connection": {
    "connected_at": "2026-02-15T10:30:00Z",
    "last_used_at": "2026-03-10T14:22:00Z",
    "scopes_granted": ["openid", "age_verification", "profile"]
  }
}

Combining scopes

You can request multiple scopes in a single authorization request. Here is an example with all scopes:

// scope: openid profile email age_verification connections
// All scopes combined
{
  "sub": "xid_abc123def456",
  "preferred_username": "alex_t",
  "display_name": "Alex T.",
  "picture": "https://api.xident.io/avatars/xid_abc123def456.jpg",
  "created_at": "2026-01-10T08:00:00Z",
  "email": "alex@example.com",
  "email_verified": true,
  "age_verified": true,
  "age_bracket": "18+",
  "age_brackets_verified": ["12+", "15+", "18+"],
  "verification_level": "ml",
  "verified_at": "2026-02-15T10:30:00Z",
  "connection": {
    "connected_at": "2026-02-15T10:30:00Z",
    "last_used_at": "2026-03-10T14:22:00Z",
    "scopes_granted": ["openid", "profile", "email", "age_verification", "connections"]
  }
}

Common use case: age-gated content

For most age verification use cases, you only need openid and age_verification. This is the minimal scope set for age-gated content:

// Most common use case: age-gated content
// scope: openid age_verification

const params = new URLSearchParams({
  client_id: 'xc_abc123',
  redirect_uri: 'https://yoursite.com/callback',
  response_type: 'code',
  scope: 'openid age_verification', // Just these two
  code_challenge: codeChallenge,
  code_challenge_method: 'S256',
  state: state,
});

Fetching claims

After obtaining an access token, you can fetch claims from the UserInfo endpoint or decode them from the ID token (if the scope was included in the authorization request).

// Fetch claims from the UserInfo endpoint
const response = await fetch('https://api.xident.io/oauth/userinfo', {
  headers: {
    Authorization: `Bearer ${accessToken}`,
  },
});

const claims = await response.json();

// Check age verification
if (claims.age_verified && claims.age_brackets_verified.includes('18+')) {
  // User is verified 18+
  grantAccess();
} else {
  // User is not verified for this age bracket
  showAgeRestrictionMessage();
}

Privacy guarantees

  • No face images — Face data never leaves the user's device during ML verification
  • No documents — Document images are deleted immediately after OCR processing
  • No birthdate — Only the verified age bracket is stored and shared
  • No cross-site tracking — Your site cannot see connections to other sites
  • User-controlled — Users can revoke your site's access at any time from their Xident account
  • GDPR-compliant — Hard-delete on account deletion, no data retention

Next steps