/** * JWT Issuer and JWKS generation for TypeScript MCP servers. * Uses jose (industry-standard, zero-dep, Web Crypto compatible). * * Two signing modes, selected at construction: * * - LOCAL single-user (credentialSecret unset): RSA-2048, RS256, kid="key-1", * keys generated on first run and persisted to disk so they survive restarts * on a real machine. Behavior is unchanged from the pre-stability-fix code. * * - HTTP multi-user (credentialSecret set): Ed25519, EdDSA, signing key DERIVED * deterministically from CREDENTIAL_SECRET via HKDF-SHA256. No disk I/O. Every * container replica converges on the same key without a shared volume or * external secret store, so OAuth tokens survive container recreation * (Watchtower :latest redeploys). kid is the base64url SHA-256 thumbprint of * the raw public key. * * The two modes are different deployments that never exchange tokens * (iss/aud are server-scoped); the per-mode algorithm split is permanent and * intentional. Each process runs exactly one algorithm; the verify path uses * that single alg, never a {RS256, EdDSA} union. */ import * as jose from 'jose'; export declare class JWTIssuer { private serverName; private keysDir; private privateKeyPath; private publicKeyPath; private credentialSecret; alg: 'RS256' | 'EdDSA'; private kid; private privateKey; private publicKey; private okpX; private _initialized; constructor(serverName: string, keysDir?: string, credentialSecret?: string | null); /** Must call before using issuer — loads/generates (RSA) or derives (EdDSA) keys. */ init(): Promise; private deriveEddsaKeys; private loadOrGenerateRsaKeys; /** * Return JWKS payload for /.well-known/jwks.json. Always a `keys` array * (multi-key-aware for future rotation). RSA mode emits an RSA JWK; EdDSA * mode emits an OKP JWK. */ getJwks(): Promise; /** Issue a JWT access token (typ="access") signed with the active alg. */ issueAccessToken(sub: string, expiresInSeconds?: number): Promise; /** * Issue a JWT refresh token (typ="refresh") signed with the active alg. * * Defaults to a 1-year (31536000s) lifetime so long-running MCP clients can * mint fresh access tokens without forcing the user back through the browser * PKCE flow. The access token stays short-lived (1h); the refresh token is the * renewal credential and is rotated on every use (the /token refresh handler * issues a new refresh token each time), so the security control is rotation, * not a short TTL. A short refresh TTL was the residual re-auth driver: a * self-hosted server a user touches only intermittently (less than once a * month) would silently expire its refresh token between sessions, forcing a * fresh browser OAuth tab on the next use. With a 1-year floor, only a * genuinely long idle gap re-prompts. Same key / iss / aud as access tokens; * the typ claim is the only thing distinguishing them, and verifyAccessToken * rejects typ="refresh" so a refresh token can never be used as an access * token at the /mcp resource. */ issueRefreshToken(sub: string, expiresInSeconds?: number): Promise; /** * Verify a JWT access token and return its payload. Throws on failure (bad * signature, wrong issuer/audience, expired) — the same jose errors existing * callers already catch. The verify path uses the single active algorithm, * never a union. Additionally rejects tokens whose typ claim is "refresh" * (throwing jose.errors.JWTClaimValidationFailed) so a refresh token cannot * be replayed as an access token. Tokens with typ="access" OR a missing typ * claim are accepted (the latter keeps already-issued pre-refresh-support * tokens valid). */ verifyAccessToken(token: string): Promise; /** * Verify a JWT refresh token and return its payload. Same key / audience / * issuer checks as verifyAccessToken and throws the same jose errors on * failure; uses the single active algorithm. Additionally asserts * typ=="refresh" (throwing jose.errors.JWTClaimValidationFailed otherwise) so * an access token can never be exchanged at the refresh grant. */ verifyRefreshToken(token: string): Promise; } //# sourceMappingURL=jwt-issuer.d.ts.map