Token Validation
ChainIT issues three artifacts from the token endpoint. Treat each one correctly:
| Credential | Format | Signature / verification |
|---|---|---|
| Access token | JWT | RS256 — verify using the org-scoped JWKS URL below |
| ID token | JWT | RS256 — verify using the same JWKS URL (signed by the same key) |
| Refresh token | Opaque string (not a JWT) | Do not parse as JWT; store and send only to the token endpoint |
The JWKS Endpoint
Public signing keys for both access tokens and ID tokens are published in JWKS format. The URL includes your organization ID (orgId).
URL pattern:
https://api.chainit.online/oauth/<orgId>/.well-known/jwks.json
Example:
https://api.chainit.online/oauth/00000000-0000-0000-0000-000000000000/.well-known/jwks.json
Replace <orgId> with your organization UUID (the same value shown in the Developer Portal under Application → Advanced next to JWKS URL).
Always cache the JWKS response to avoid making a network request for every token validation. Most libraries handle this automatically with a background refresh.
ID tokens are signed with the same RS256 key as access tokens. Use the JWKS
URL above to verify both; you do not need your clientSecret to validate
ID tokens.
Access token validation (RS256 + JWKS)
A valid access JWT should pass the following checks:
- Signature: Verify RS256 using the public key from JWKS (
kidin the JWT header must match a key in the set). - Issuer (
iss): Must exactly match theissvalue inside the token for your environment (typically your ChainIT API base URL). - Audience (
aud): Must contain your application’sclientIdwhere applicable. - Expiration (
exp): Current time must be before the expiration timestamp. - Issued at (
iat): Allow small clock skew; reject tokens from the future.
ID token validation (RS256 + JWKS)
ID tokens are signed with the same RS256 key as access tokens. Verify them against the same JWKS endpoint and enforce the standard OIDC claims:
- Signature: Verify RS256 using the public key from JWKS (match the
kidin the JWT header). - Issuer (
iss): Must match your ChainIT issuer URL. - Audience (
aud): Must equal your application’sclientId. - Expiration (
exp): Current time must be before the expiration timestamp. - Nonce (
nonce): If you supplied anonceon the authorize request, it must match the token’snonceclaim.
Never verify an ID token using your clientSecret — ChainIT signs ID tokens asymmetrically (RS256).
Refresh tokens
The refresh token is an opaque credential, not a JWT. Do not base64-decode it expecting a payload; store it securely and present it only when calling the token endpoint to obtain new tokens.
Implementation examples (access token)
- Node.js
- Go
Using jwks-rsa and jsonwebtoken
const jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');
// Replace <<your_org_id>> with your organization UUID
const JWKS_URI = 'https://api.chainit.online/oauth/<<your_org_id>>/.well-known/jwks.json';
const client = jwksClient({ jwksUri: JWKS_URI });
function getKey(header, callback){
client.getSigningKey(header.kid, function(err, key) {
const signingKey = key.publicKey || key.rsaPublicKey;
callback(null, signingKey);
});
}
const options = {
audience: '<<your_client_id>>',
issuer: 'https://api.chainit.online',
algorithms: ['RS256']
};
jwt.verify(accessToken, getKey, options, function(err, decoded) {
if (err) {
console.error('Validation failed:', err.message);
} else {
console.log('Token is valid:', decoded);
}
});
Replace <<your_org_id>> and audience with values from your environment and token payload.
ID token (RS256 + JWKS) sketch
The same getKey + JWKS setup works for ID tokens — only the audience and accepted algorithm matter:
jwt.verify(idToken, getKey, {
algorithms: ['RS256'],
audience: '<<your_client_id>>',
issuer: 'https://api.chainit.online'
}, (err, decoded) => { /* ... */ });
Using golang-jwt/jwt (access token)
import (
"context"
"fmt"
"github.com/golang-jwt/jwt/v5"
"github.com/lestrrat-go/jwx/jwk"
)
func validateAccessToken(tokenString string) (*jwt.Token, error) {
// Replace <<your_org_id>> with your organization UUID
jwksURL := "https://api.chainit.online/oauth/<<your_org_id>>/.well-known/jwks.json"
keySet, err := jwk.Fetch(context.Background(), jwksURL)
if err != nil {
return nil, err
}
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
kid := token.Header["kid"].(string)
key, ok := keySet.LookupKeyID(kid)
if !ok {
return nil, fmt.Errorf("unknown kid: %s", kid)
}
var rawKey interface{}
if err := key.Raw(&rawKey); err != nil {
return nil, err
}
return rawKey, nil
})
return token, err
}
Introspection endpoint
If you cannot perform local validation (for example, your platform cannot use RS256 + JWKS), you can use the token introspection endpoint for access tokens.
Endpoint: POST /public-api/v1/auth/introspect
POST /public-api/v1/auth/introspect
Content-Type: application/json
Authorization: Basic {base64(clientId:clientSecret)}
{
"token": "eyJhbGciOiJSUzI1NiIsIn..."
}
Response:
{
"active": true,
"scope": "openid profile email",
"client_id": "<<your_client_id>>",
"sub": "user-uuid",
"exp": 1715600000
}