accesstoken

The access token core microservice generates short-lived JWTs signed with ephemeral Ed25519 keys for internal actor propagation. On each incoming request, the HTTP ingress proxy exchanges the external bearer token for an internal access token. The access token’s claims serve as the basis for authorization decisions throughout the call stack.

Token Minting

The Mint endpoint signs a JWT with a given set of claims. The token’s lifetime is derived from the request’s time budget, falling back to DefaultTokenLifetime (default 20s) if no budget is set, and capped at MaxTokenLifetime (default 15m).

The minted token includes several critical claims that are set automatically and cannot be overridden:

ClaimDescription
issSet to https://access.token.core, identifying the access token service as the issuer
idpPreserves the original iss of the input claims, identifying the identity provider (typically the bearer token service)
iatIssued-at timestamp, backdated 5 seconds to account for clock skew
expExpiration timestamp, with 5 seconds of grace for clock skew
jtiA unique token identifier for replay protection
microbusAn indicator that the token issuer is an internal Microbus service

Claims Transformation

Claims transformers can be registered to enrich the token with dynamic claims before signing. Transformers are called in the order they were added and mutate the claims map in place. They see the original input claims but cannot override the critical claims listed above, which are set after transformation.

accesstoken.NewService().Init(func(svc *accesstoken.Service) error {
    err := svc.AddClaimsTransformer(func(ctx context.Context, claims jwt.MapClaims) (err error) {
        userID := int(claims["userid"].(float64))
        user, err := usersapi.NewClient(svc).Load(ctx, userID)
        if err != nil {
            return errors.Trace(err)
        }
        claims["roles"] = user.Roles
        claims["groups"] = user.Groups
        return nil
    })
    return err
})

Transformers must be registered during initialization, before the service starts, as part of the full auth setup.

Key Management

Each replica generates its own ephemeral Ed25519 key pair in memory on startup. Keys are never persisted or shared. Key rotation happens on a configurable interval (default 6h, minimum 2h). Each replica keeps at most two keys (current + previous) to allow verification of tokens signed just before rotation. A freshly rotated key is published in JWKS before it signs anything: for a short activation delay the replica keeps signing with the previous key while the new one is already advertised, so verifiers have had time to fetch it before the first token bearing its kid arrives.

The JWKS endpoint aggregates public keys from all replicas by multicasting to LocalKeys, returning them in standard JWKS format. Downstream connectors cache these keys and refresh on an unknown kid — but that refresh is debounced per issuer (at most one fetch per second), so a flood of tokens carrying garbage kids cannot amplify into a storm of JWKS fetches against the token service. The JWKS response itself is marked cacheable for one second for the same reason.

Configuration

PropertyDefaultDescription
KeyRotationInterval6hDuration between Ed25519 key rotations (minimum 2h)
DefaultTokenLifetime20sToken lifetime when no time budget is present
MaxTokenLifetime15mMaximum token lifetime regardless of time budget