UserInfo
UserInfo Endpoint
After obtaining an access token via token exchange, use it to retrieve the authenticated user's profile data. The claims returned are filtered based on the scopes granted to the token.
Two authentication methods are supported:
| Method | HTTP | Use case |
|---|---|---|
| Bearer token | GET /api/oauth/userinfo | Browser-side, standard OIDC |
| Client credentials | POST /api/oauth/userinfo | Server-to-server |
Method 1: Bearer Token (GET)
Include the access token in the Authorization header:
GET /api/oauth/userinfo
Authorization: Bearer sx_it_...
Example Request
curl -X GET https://api.selorax.io/api/oauth/userinfo \
-H "Authorization: Bearer sx_it_..."Response: Customer
A customer with scopes openid profile email phone store:
{
"sub": "customer:42",
"name": "John Doe",
"picture": "https://r2.selorax.io/avatars/user_42.jpg",
"email": "[email protected]",
"email_verified": true,
"phone_number": "+8801712345678",
"phone_number_verified": true,
"store_id": 22,
"store_name": "Florist BD",
"role": "customer"
}Response: Merchant
A merchant with scopes openid profile email store:
{
"sub": "merchant:7",
"name": "Jane Admin",
"picture": null,
"email": "[email protected]",
"email_verified": true,
"store_id": 22,
"store_name": "Florist BD",
"role": "admin"
}Method 2: Client Credentials (POST)
For server-to-server token validation, your backend can send the client_id, client_secret, and access_token directly — no Authorization header needed. This is similar to Google's tokeninfo pattern.
POST /api/oauth/userinfo
Content-Type: application/json
Request Body
{
"client_id": "sx_oc_...",
"client_secret": "sx_os_...",
"access_token": "sx_it_..."
}| Field | Required | Description |
|---|---|---|
client_id | Yes | Your OAuth client ID |
client_secret | Yes | Your OAuth client secret |
access_token | Yes | The user's access token to look up |
Example Request
curl -X POST https://api.selorax.io/api/oauth/userinfo \
-H "Content-Type: application/json" \
-d '{
"client_id": "sx_oc_a1b2c3...",
"client_secret": "sx_os_d4e5f6...",
"access_token": "sx_it_g7h8i9..."
}'Security
- The access token must belong to your client. If the token was issued to a different client, the request is rejected with
403 token_mismatch. - Both
client_idandclient_secretare validated before the token is resolved.
Claims by Scope
The response only includes claims for scopes that were granted to the access token:
| Scope | Claims | Description |
|---|---|---|
openid | sub | User identifier. Format: {user_type}:{user_id} (e.g. customer:42, merchant:7) |
profile | name, picture | Display name and avatar URL. picture may be null. |
email | email, email_verified | Email address and whether it's been verified. |
phone | phone_number, phone_number_verified | Phone number (E.164 format) and verification status. For merchants, phone_number_verified is always true. For customers, it reflects the actual verification status from the database. |
store | store_id, store_name, role | Store context. For merchants, role comes from store_admins_link. For customers, role is always "customer". |
If a scope was not granted, its claims are omitted from the response entirely.
Merchant Store Context
Merchants may manage multiple stores. The store scope returns the store context that was specified during authorization:
- If
store_idwas provided in the authorize request → that store's data is returned - The platform validates the merchant has access to the specified store via
store_admins_link
Example: Full Integration (Node.js)
Browser-side (Bearer Token)
// After token exchange, fetch user profile from the browser
async function getSeloraXUser(accessToken) {
const res = await fetch('https://api.selorax.io/api/oauth/userinfo', {
headers: { 'Authorization': `Bearer ${accessToken}` },
});
if (!res.ok) {
throw new Error(`UserInfo failed: ${res.status}`);
}
return await res.json();
}Server-side (Client Credentials)
// Validate a user's access token from your backend
async function validateSeloraXToken(accessToken) {
const res = await fetch('https://api.selorax.io/api/oauth/userinfo', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
client_id: process.env.SELORAX_CLIENT_ID,
client_secret: process.env.SELORAX_CLIENT_SECRET,
access_token: accessToken,
}),
});
if (!res.ok) {
throw new Error(`Token validation failed: ${res.status}`);
}
const user = await res.json();
return {
id: user.sub, // "customer:42"
name: user.name, // "John Doe"
email: user.email, // "[email protected]"
avatar: user.picture, // URL or null
store: user.store_name, // "Florist BD"
};
}:::tip Use the POST method from your backend to validate tokens server-to-server. This avoids exposing your access token in browser-side requests and lets your server verify the token belongs to your client. :::
Error Responses
GET (Bearer Token)
| Status | Code | Description |
|---|---|---|
| 401 | missing_token | No Authorization: Bearer header provided |
| 401 | invalid_token | Token is expired, revoked, or malformed |
| 404 | — | User not found in the database |
| 500 | auth_error | Internal error during token resolution |
POST (Client Credentials)
| Status | Code | Description |
|---|---|---|
| 400 | invalid_request | Missing client_id, client_secret, or access_token |
| 401 | invalid_client | Client not found or wrong client_secret |
| 401 | invalid_token | Token is expired, revoked, or has invalid format |
| 403 | token_mismatch | Access token was issued to a different client |
| 404 | — | User not found in the database |
Example
{
"message": "Invalid or expired access token.",
"code": "invalid_token",
"status": 401
}Caching
Access tokens are cached in Redis for 5 minutes (keyed by 16-character prefix). Repeated UserInfo calls within the cache window are fast — no database query is needed for token resolution.