Reading custom claims
Custom claims are nested under the namespace https://trymellon.dev/claims in the session JWT. The namespace prevents collisions with reserved OIDC claims and signals “vendor-defined” to any standards-aware library.
For the SDK side (defining schema, passing claims at login) see SDK custom claims.
JWT structure
After offline verification, the payload looks like:
{
"iss": "https://api.trymellonauth.com",
"sub": "user_…",
"aud": "client_…",
"iat": 1717000000,
"exp": 1717003600,
"https://trymellon.dev/claims": {
"role": "admin",
"company_id": 42
}
}
Node.js (jose)
import { createRemoteJWKSet, jwtVerify } from 'jose';
const JWKS = createRemoteJWKSet(
new URL('https://api.trymellonauth.com/.well-known/jwks.json'),
);
const TM_CLAIMS = 'https://trymellon.dev/claims' as const;
interface AppClaims {
role?: 'admin' | 'member';
company_id?: number;
wallet_address?: string;
}
export async function readClaims(token: string): Promise<AppClaims> {
const { payload } = await jwtVerify(token, JWKS, {
issuer: 'https://api.trymellonauth.com',
});
return (payload[TM_CLAIMS] ?? {}) as AppClaims;
}
Go (lestrrat-go/jwx)
const TMClaimsKey = "https://trymellon.dev/claims"
type AppClaims struct {
Role string `json:"role,omitempty"`
CompanyID int `json:"company_id,omitempty"`
WalletAddress string `json:"wallet_address,omitempty"`
}
func ReadClaims(token jwt.Token) (AppClaims, error) {
raw, ok := token.Get(TMClaimsKey)
if !ok {
return AppClaims{}, nil
}
bytes, err := json.Marshal(raw)
if err != nil {
return AppClaims{}, err
}
var c AppClaims
return c, json.Unmarshal(bytes, &c)
}
Python (python-jose)
TM_CLAIMS = "https://trymellon.dev/claims"
def read_claims(decoded_payload: dict) -> dict:
return decoded_payload.get(TM_CLAIMS, {})
# Usage
payload = jwt.decode(token, jwks(), algorithms=["RS256"], audience=AUD)
claims = read_claims(payload)
role = claims.get("role")
Defensive reads
The schema you configured in the dashboard is enforced at issuance. By the time a token reaches your backend the claims are already shape-validated — but treat them as untrusted input anyway:
const role = claims.role === 'admin' ? 'admin' : 'member';
This avoids cascading failures if you later change the schema and old tokens still carry the previous shape.