Overview
Theverify_grant_token() function verifies a Grantex grant token’s signature
and claims locally after retrieving the published JSON Web Key Set (JWKS). It
avoids the online verification/revocation endpoint, but the current standalone
helper fetches the remote JWKS on every invocation and is not network-free.
This is ideal for service-side verification where latency matters and you want to avoid an API round-trip for every request.
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 issuer_did explicitly.Usage
Import
Options
VerifyGrantTokenOptions configures how the token is verified:
Response
verify_grant_token() returns a VerifiedGrant frozen dataclass:
Fields are read from the standard claims (
scope, client_id, act, urn:grantex:grant) first;
see spec/grant-token-0.6.md.
Algorithm
Tokens signed with RS256 or ES256 are accepted (grantex.GRANT_TOKEN_ALGORITHMS). alg: none, HS256 and every other algorithm are rejected, and algorithms can only narrow the list.
The key is the JWK Set entry named by the token’s kid whose type matches the algorithm: an RSA key for RS256, an EC key on P-256 for ES256. A key published with a different alg or a use other than sig is never used, and there is no fallback to another key.
JWKS fetch limits
grantex==0.6.1 includes these limits and the DID issuer
checks when bounded_jwks_fetch=True is set. The option remains off by
default; choose it explicitly when verifying untrusted remote issuers.bounded_jwks_fetch=True, each fetch is bounded, with the same limits as the
TypeScript SDK’s boundedJwksFetch: true:
A refused fetch raises
GrantexTokenError naming the endpoint and the reason,
and nothing is cached, so the next verification fetches again. The key-count
limit leaves room for the grantex-YYYY-MM aliases a self-hosted auth service
publishes (JWT_LEGACY_KID_MONTHS). grantex.decisions.verify_decision_grant
and verify_decision_grants take the same bounded_jwks_fetch keyword for
the keys they read from jwks_uri.
Without the option, the key set is read as in earlier releases: any 2xx
response of any size, media type and number of keys, with a 10-second timeout
for each network operation. Bounded and unbounded key sets for the same URL
are cached separately.
DID issuers
Withbounded_jwks_fetch=True, issuer_did 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 raises
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 jwks_uri. For a local or private issuer, set
jwks_uri and issuer instead. issuer_did=None means no DID. The TypeScript
SDK applies the same rules with boundedJwksFetch: true, and treats
issuerDid: null as None.
Without the option, issuer_did is read as in earlier releases: a value that
does not start with did:web: is ignored in favour of jwks_uri, 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).
Examples
Basic Verification
Require Specific Scopes
With Audience and Clock Tolerance
Verifying Delegated Tokens
Delegated tokens include additional claims for the delegation chain:Error Handling
verify_grant_token() raises GrantexTokenError in the following cases:
- The token header uses an algorithm other than RS256 or ES256, or one excluded by
algorithms - The JWKS endpoint is unreachable or returns invalid data
- With
bounded_jwks_fetch=True, the JWKS response exceeds a fetch limit: not200, not JSON, encoded, larger than 64 KiB, more than 128 keys, or slower than 5 seconds - With
bounded_jwks_fetch=True,issuer_didis not a usable did:web issuer - No key of the type the algorithm requires is found in the JWKS under the token’s
kid - The token signature is invalid
- The token is expired
- The token issuer does not match the expected issuer
- The token audience does not match
audience, when configured - Required claims (
jti,sub,agt,dev,scp,iat,exp) are missing - Required scopes are not present in the token