Skip to main content

Overview

verifyGrantToken() is a standalone function that verifies signatures and claims locally after retrieving the published JWKS (JSON Web Key Set). It validates the RS256 or ES256 signature, issuer, expiry, and optional scopes and audience. The current standalone helper resolves the remote JWKS on each invocation, so it is not a network-free hot path. Use it when signature-and-claim validation without a revocation lookup is the right trade-off. It avoids POST /v1/tokens/verify, but the JWKS endpoint must be reachable for each standalone helper call.
The standalone helper may contact the JWKS endpoint when it resolves keys. Keep that endpoint reachable from the verifying service; token validation itself is performed locally and does not call POST /v1/tokens/verify.
Hosted tokens use the canonical issuer https://grantex.dev, even though the stable JWKS URL is served from https://api.grantex.dev. The SDK handles this alias automatically. For self-hosted deployments, it derives the expected issuer from the JWKS URL unless you set issuer or issuerDid explicitly.

Import

This function does not require a Grantex client instance.

Parameters

string
required
The grant token JWT string to verify.
VerifyGrantTokenOptions
required
Verification options.

VerifyGrantTokenOptions

string
required
The JWKS endpoint URL. For the hosted service, use https://api.grantex.dev/.well-known/jwks.json.
string[]
If provided, the function throws GrantexTokenError when the token is missing any of these scopes.
string
Expected aud claim. If provided, verification fails when the token’s audience does not match.
string
Expected iss claim. The hosted JWKS alias automatically expects https://grantex.dev; custom JWKS URLs derive the issuer from their URL when this option is omitted.
string | null
A did:web issuer identifier. When provided, the JWKS is read from https://<host>[:<port>][/<path>]/.well-known/jwks.json instead of jwksUri, and the expected issuer is that location without the suffix. An explicit issuer still takes precedence. With boundedJwksFetch: true it must name a public domain; see DID issuers. null, like leaving it out, means no DID, as issuer_did=None does in the Python SDK.
boolean
Apply the JWKS fetch limits and the DID issuer checks. Defaults to false in these releases; a later major release may make true the default, with false as the opt-out. Available in @grantex/sdk@0.7.1.
number
Optional clock-skew tolerance, in seconds, for time-based JWT claims.
string
Thumbprint of the key the caller proved possession of (for example with a verified DPoP proof). The token’s cnf.jkt must match it. The verifier does not check DPoP proofs.
boolean
Fail closed unless proofJkt is given and matches cnf.jkt. Without it, cnf is returned but not enforced.
boolean
Read legacy claim aliases (agt, dev, grnt, scp, parentAgt, parentGrnt, delegationDepth) when the standard claim is absent, emitting a DeprecationWarning (code GRANTEX_LEGACY_CLAIM) once per alias. Defaults to true in 0.6 and false in 0.7. With false, only standard claims are read and typ must be at+jwt. A token whose standard claim and alias disagree is always rejected.
Array<'RS256' | 'ES256'>
Narrow the accepted signature algorithms. The default is GRANT_TOKEN_ALGORITHMS (['RS256', 'ES256']); any other value throws GrantexTokenError. The key is the JWK Set entry named by kid whose type matches the algorithm (RSA for RS256, EC P-256 for ES256); alg: none and HS256 are always rejected.

JWKS fetch limits

@grantex/sdk@0.7.1 includes these limits and the DID issuer checks when boundedJwksFetch: true is set. The option remains off by default; choose it explicitly when verifying untrusted remote issuers.
The key set comes from whichever endpoint the verifier is pointed at. With boundedJwksFetch: true, each fetch is bounded, with the same limits as the Python SDK’s bounded_jwks_fetch=True:
A refused fetch throws GrantexTokenError naming the endpoint and the reason, and nothing is cached, so the next verification fetches again. JOSE’s cache and refresh behaviour is unchanged. The key-count limit leaves room for the grantex-YYYY-MM aliases a self-hosted auth service publishes (JWT_LEGACY_KID_MONTHS). Decision-grant verification (verifyDecisionGrant, verifyDecisionGrants) with jwksUri and boundedJwksFetch: true reads keys within the same limits. Without the option, the key set is read as in earlier releases, with JOSE’s createRemoteJWKSet defaults: an HTTP 200 of any size, media type and number of keys, within JOSE’s 5-second timeout. Bounded and unbounded key sets for the same URL are cached separately.

DID issuers

With boundedJwksFetch: true, issuerDid follows the did:web method specification (§2.3, §2.5.2): the identifier after did:web: is a fully qualified domain name, optionally followed by a port whose colon is percent-encoded (%3A) and by path segments separated by colons. A DID is written in ASCII (§3.5), so an internationalized domain name is given in its IDNA A-label form, beginning xn--. The DID decides whose keys are trusted, so the SDK throws GrantexTokenError, before any request, for an IP address, localhost or a name under .localhost, .local, .home.arpa or .internal, a single-label host, user information (@), a port outside 1–65535, a path segment other than letters, digits, ., - and _ (or . / ..), any character outside ASCII, and any value that is not a did:web identifier. A non-ASCII name is refused rather than converted, because conversion can turn a different character into an ASCII one (the Kelvin sign, U+212A, into k) and so fetch keys from a host the DID does not spell. It is never ignored in favour of jwksUri. For a local or private issuer, set jwksUri and issuer instead. The Python SDK applies the same rules with bounded_jwks_fetch=True. Without the option, issuerDid is read as in earlier releases: a value that does not start with did:web: is ignored in favour of jwksUri, and the rest of the identifier is used as written, each : becoming /, with none of the checks above (so a percent-encoded port is not decoded).

Response: VerifiedGrant

string
Unique token ID (the jti JWT claim).
string
The grant record ID (from the grnt claim, falls back to jti).
string
The end-user who authorized the agent (the sub claim).
string
The agent’s decentralized identifier (the agt claim).
string
The developer organization that owns the agent (the dev claim).
string[]
The scopes granted to the agent (the scp claim).
number
Token issued-at timestamp in seconds since the Unix epoch.
number
Token expiry timestamp in seconds since the Unix epoch.
WebAuthnGrantEvidence | undefined
Signed issuer reference to the original passkey assertion, when portable evidence was captured. This does not replace online revocation checks or independent verification of the opt-in VC’s assertion.
string
The parent agent’s DID, present only for delegated grants.
string
The parent grant ID, present only for delegated grants.
number
The delegation depth (0 = root grant, 1 = first-level delegation, etc.). Present only for delegated grants.

Error handling

verifyGrantToken() throws GrantexTokenError in the following cases:
  • The JWT signature is invalid
  • The token uses an algorithm other than RS256 or ES256, or names a key of the wrong type
  • The token has expired
  • The token issuer does not match the expected issuer
  • Required claims (jti, sub, agt, dev, scp, iat, exp) are missing
  • The token is missing one or more requiredScopes
  • The audience does not match
  • With boundedJwksFetch: true, the JWKS response exceeds a fetch limit: not 200, not JSON, larger than 64 KiB, more than 128 keys, or slower than 5000 ms
  • With boundedJwksFetch: true, issuerDid is not a usable did:web issuer

JWT claims mapping

The following table shows how JWT claims map to VerifiedGrant fields:

Comparison with online verification

Ownership

Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: sanjeev@orchestrum.in or mishra.sanjeev@gmail.com.
Last modified on September 28, 2026