description: Validate a TrustNet ID token: signature, issuer, audience, nonce and the PPID.
Validate an ID token
Never trust an ID token because it decodes to JSON. Validate the signature and
the registered claims before using sub or any disclosed claim.
Checks to perform
| Check | Expected |
|---|---|
| Signature | Verify with the key from the issuer's JWKS; allow only RS256. |
Issuer (iss) |
Exactly https://id.trustnet.app (staging: https://id.stg.trustnet.test). |
Audience (aud) |
Contains your client_id. |
Expiry (exp) |
In the future; allow a small clock skew (for example 60 seconds). |
Issued at (iat) |
Not in the future beyond your clock skew. |
| Nonce | Equals the nonce you sent in the authorization request. |
| Algorithm | RS256 only. Reject none and unexpected algorithms. |
The JWKS endpoint is:
https://id.trustnet.app/.well-known/jwks.json
Cache the keys for a short time and select by the token's kid. The issuer may
rotate signing keys; do not pin a key permanently.
Node.js
npm install jose
import { createRemoteJWKSet, jwtVerify } from "jose";
const JWKS = createRemoteJWKSet(
new URL("https://id.trustnet.app/.well-known/jwks.json"),
);
const { payload } = await jwtVerify(idToken, JWKS, {
issuer: "https://id.trustnet.app",
audience: clientId,
algorithms: ["RS256"],
clockTolerance: 60,
});
if (!expectedNonce || payload.nonce !== expectedNonce) {
throw new Error("ID token nonce mismatch");
}
// payload.sub is the PPID; payload.sid is the session id for back-channel logout.
Python
python3 -m pip install pyjwt[crypto]
import jwt
jwks_client = jwt.PyJWKClient(
"https://id.trustnet.app/.well-known/jwks.json"
)
signing_key = jwks_client.get_signing_key_from_jwt(id_token)
claims = jwt.decode(
id_token,
signing_key.key,
algorithms=["RS256"],
audience=client_id,
issuer="https://id.trustnet.app",
leeway=60,
)
if not expected_nonce or claims.get("nonce") != expected_nonce:
raise ValueError("ID token nonce mismatch")
What to do with the values
sub— the PPID. Use it as your local user key for this RP. It is stable per tenant and persona, and it is not comparable across tenants or personas.sid— store it with the user's session. Back-channel logout tokens carry this value so you can terminate the matching session.- Claims — use only claims present in the token. A missing claim means the user did not approve it, the persona does not hold it, or it was not requested. Do not substitute a value from another source.
email_verified/phone_number_verified— booleans; onlytruemeans the value is verified.
Staging certificates
Local staging uses a private CA. Point your runtime at the CA before validating:
# curl
curl --cacert /path/to/trustnet-staging-root.crt ...
# Node.js
NODE_EXTRA_CA_CERTS=/path/to/trustnet-staging-root.crt node app.js
# Python requests/PyJWT
REQUESTS_CA_BUNDLE=/path/to/trustnet-staging-root.crt python3 app.py
Production uses a public certificate and needs no extra trust configuration.