Skip to main content

Scopes and Claims

Scopes define what data your application can access, and claims are the actual pieces of user information returned. Understanding both is essential for requesting the right permissions.

What are Scopes?

Scopes are OAuth 2.0 permissions that your application requests from the user. They determine what data your app can access.

Example authorization request:

GET /auth?scope=openid profile email&...

Standard Scopes

openid (Required)

The openid scope is required for OIDC authentication. It indicates that you want an ID token.

Returns:

  • sub - Unique user identifier
{
"sub": "user_123abc"
}

profile

Access to the user's basic profile information.

Returns:

  • name - Full name
  • preferred_username - Username or handle
  • picture - Profile picture URL
{
"sub": "user_123abc",
"name": "John Doe",
"preferred_username": "johnd",
"picture": "https://cdn.consentkeys.com/avatars/..."
}

email

Access to the user's email address.

Returns:

  • email - Email address
  • email_verified - Whether email is verified (always true for ConsentKeys)
{
"sub": "user_123abc",
"email": "john@example.com",
"email_verified": true
}

address

Access to the user's physical address (if provided).

Returns:

  • address - Formatted address object
{
"sub": "user_123abc",
"address": {
"formatted": "123 Main St, Springfield, IL 62701, USA",
"street_address": "123 Main St",
"locality": "Springfield",
"region": "IL",
"postal_code": "62701",
"country": "USA"
}
}

Scope Combinations

You can request multiple scopes by separating them with spaces:

scope=openid profile email

Common combinations:

CombinationUse Case
openidMinimal authentication (just user ID)
openid profileBasic profile display
openid emailEmail-based identification
openid profile emailFull user profile (recommended)
openid profile email addressComplete user information

Claims Explained

Claims are name-value pairs that contain information about the user. They appear in:

  • ID tokens (JWT)
  • UserInfo endpoint response

Standard Claims

ClaimTypeDescriptionScope Required
substringUnique user identifieropenid
issstringToken issuer (ConsentKeys)openid
audstringAudience (your client_id)openid
expnumberExpiration timestampopenid
iatnumberIssued at timestampopenid
namestringFull nameprofile
preferred_usernamestringUsernameprofile
picturestringAvatar URLprofile
emailstringEmail addressemail
email_verifiedbooleanEmail verified statusemail
addressobjectPhysical addressaddress

ID Token Example

When you exchange an authorization code for tokens, the ID token contains these claims:

{
"sub": "user_123abc",
"iss": "https://pseudoidc.consentkeys.com",
"aud": "ck_your_client_id",
"exp": 1703980800,
"iat": 1703977200,
"nonce": "random_replay_protection",
"name": "John Doe",
"preferred_username": "johnd",
"picture": "https://cdn.consentkeys.com/avatars/...",
"email": "john@example.com",
"email_verified": true
}

UserInfo Response Example

The /userinfo endpoint returns the same claims (without token-specific ones):

{
"sub": "user_123abc",
"name": "John Doe",
"preferred_username": "johnd",
"picture": "https://cdn.consentkeys.com/avatars/...",
"email": "john@example.com",
"email_verified": true
}

The consent screen dynamically shows what data you're requesting based on scopes:

Requested scopes: openid profile email

┌────────────────────────────────────────┐
│ │
│ YourApp wants to access: │
│ │
│ ✓ Your email address │
│ ✓ Your profile information │
│ (name, username, profile picture) │
│ │
│ [Allow] [Deny] │
│ │
└────────────────────────────────────────┘

Requesting Minimal Scopes

Best practice: Only request the scopes you actually need.

❌ Over-requesting

// Don't request everything if you only need email
const scopes = 'openid profile email address';

✅ Minimal Request

// Only request what you need
const scopes = 'openid email';

Benefits:

  • Higher consent approval rates
  • Better user trust
  • Compliance with data minimization principles

Conditional Claims

Some claims are only included when certain scopes are requested:

// Only openid scope
GET /userinfo
Authorization: Bearer <token>

// Response - minimal claims
{
"sub": "user_123abc"
}

// With profile and email scopes
// Response - additional claims
{
"sub": "user_123abc",
"name": "John Doe",
"email": "john@example.com",
"email_verified": true
}

Verifying ID Tokens

ID tokens are JWTs that must be verified before use:

1. Verify Signature

import { jwtVerify } from 'jose';

const JWKS_URL = 'https://pseudoidc.consentkeys.com/.well-known/jwks.json';

async function verifyIdToken(idToken: string) {
const JWKS = createRemoteJWKSet(new URL(JWKS_URL));

const { payload } = await jwtVerify(idToken, JWKS, {
issuer: 'https://pseudoidc.consentkeys.com',
audience: 'ck_your_client_id',
});

return payload;
}

2. Verify Claims

function validateClaims(claims: any) {
// Check expiration
if (Date.now() >= claims.exp * 1000) {
throw new Error('Token expired');
}

// Verify issuer
if (claims.iss !== 'https://pseudoidc.consentkeys.com') {
throw new Error('Invalid issuer');
}

// Verify audience
if (claims.aud !== 'ck_your_client_id') {
throw new Error('Invalid audience');
}

// Verify nonce (if used)
if (claims.nonce !== sessionStorage.getItem('nonce')) {
throw new Error('Invalid nonce');
}

return true;
}

Scope Changes

Adding Scopes Later

If you need to request additional scopes after initial authentication:

// Redirect to authorization with additional scopes
const authUrl = `https://pseudoidc.consentkeys.com/auth?
client_id=ck_your_client_id&
scope=openid profile email address& // Now requesting address
prompt=consent`; // Force consent screen

window.location.href = authUrl;

The prompt=consent parameter forces the consent screen even if the user previously approved.

Removing Scopes

To reduce requested scopes, simply omit them in future authorization requests. Previously granted permissions remain until explicitly revoked by the user.

Users can view and revoke granted permissions through:

  • The ConsentKeys user dashboard
  • Your app's settings (if you implement revocation)

Checking Current Scopes

// Decode the access token to see granted scopes
import { decodeJwt } from 'jose';

const token = 'eyJhbGciOiJSUzI1NiIs...';
const decoded = decodeJwt(token);

console.log(decoded.scope); // "openid profile email"

Revoking Access

Users can revoke your app's access at any time. Handle this gracefully:

async function callAPI() {
try {
const response = await fetch('/api/data', {
headers: {
Authorization: `Bearer ${accessToken}`
}
});

if (response.status === 401) {
// Token expired or revoked - re-authenticate
redirectToLogin();
}
} catch (error) {
console.error('API call failed:', error);
}
}

Privacy Considerations

Data Minimization

Only request and store the data you need:

// ❌ Storing everything
const user = {
sub: claims.sub,
name: claims.name,
email: claims.email,
picture: claims.picture,
// ... everything else
};

// ✅ Only storing what's needed
const user = {
id: claims.sub,
email: claims.email,
};

Pseudonymous Identifiers

The sub claim is a pseudonymous identifier:

  • Unique per user
  • Consistent across sessions
  • Cannot be reverse-engineered to reveal the email
// Example sub value
"sub": "user_7f8a9b2c1d3e4f5a6b7c8d9e0f1a2b3c"

Scope Descriptions

When requesting scopes, users see clear descriptions:

ScopeUser Sees
profile"Your profile information (name, username, profile picture)"
email"Your email address"
address"Your physical address"

Testing Scopes

Development Mode

Test different scope combinations during development:

// Test minimal scopes
const minimalScopes = 'openid';

// Test common scopes
const commonScopes = 'openid profile email';

// Test all scopes
const allScopes = 'openid profile email address';

The consent screen adapts based on requested scopes. Test each combination to ensure good UX.

Next Steps