Public beta — not for production use. Data may be wiped at any time. Questions? Contact us.
A
Axiom
/Marketplace/christiangeorgelucas/paseto-tools
Sign in

christiangeorgelucas/paseto-tools

v0.1.0Python

The paseto-tools package provides a comprehensive suite for working with PASETO (Platform-Agnostic SEcurity TOkens) and PASERK, enabling deterministic decoding, inspection, and caller-keyed verification. It allows developers to securely handle PASETO tokens, ensuring cryptographic operations are performed with caller-supplied keys while maintaining a clear separation between token inspection and verification.

pasetosecuritytoken-verificationencryptionauthenticationpythoncryptographykey-management

Use cases

  • Verify the authenticity of PASETO tokens in a web application
  • Inspect token footers for key identifiers without decryption
  • Validate claims in PASETO tokens against custom timestamps
  • Parse and manage PASERK keys for secure token handling

Nodes (9)

CheckExpirationunary
CheckExpirationInput·ExpirationResult

The CheckExpiration node verifies if a claims JSON object is expired based on its 'exp' claim, using a caller-supplied timestamp instead of the system clock. If the claims object lacks an 'exp' claim, it is never considered expired.

View source
import json

from gen.messages_pb2 import CheckExpirationInput, ExpirationResult
from gen.axiom_context import AxiomContext

from nodes.paseto_util import parse_registered_time


def check_expiration(ax: AxiomContext, input: CheckExpirationInput) -> ExpirationResult:
    """Check ONLY whether a claims JSON object is expired, per its "exp"
    claim (an ISO 8601 string, PASETO's registered-claim time convention),
    against a caller-supplied `now`. A claims object with no exp claim is
    never reported expired.
    """
    try:
        claims = json.loads(input.claims_json)
    except (json.JSONDecodeError, TypeError) as err:
        return ExpirationResult(error=f"claims_json is not valid JSON: {err}")
    if not isinstance(claims, dict):
        return ExpirationResult(error="claims_json must decode to a JSON object")

    try:
        now = parse_registered_time(input.now)
    except ValueError as err:
        return ExpirationResult(error=f"now: {err}")

    if "exp" not in claims:
        return ExpirationResult(exp_present=False, expired=False)

    try:
        exp_dt = parse_registered_time(str(claims["exp"]))
    except ValueError as err:
        return ExpirationResult(exp_present=True, error=f"invalid exp: {err}")

    delta = exp_dt - now
    seconds_remaining = int(delta.total_seconds())
    return ExpirationResult(
        exp_present=True,
        expired=seconds_remaining < 0,
        exp=str(claims["exp"]),
        seconds_remaining=seconds_remaining,
    )
DecodeUnverifiedPublicunary
TokenInput·UnverifiedClaims

The DecodeUnverifiedPublic node decodes a v#.public token's message and footer without verifying the signature, extracting the message from the signature bytes based on the version's fixed signature length. It returns structured errors for local tokens, which contain encrypted payloads that cannot be decoded without the appropriate key.

View source
import json

from gen.messages_pb2 import TokenInput, UnverifiedClaims
from gen.axiom_context import AxiomContext

from nodes.paseto_util import (
    parse_token_header,
    base64url_decode,
    try_json_loads,
    bytes_to_claims_json,
    PUBLIC_SIG_SIZE,
)


def decode_unverified_public(ax: AxiomContext, input: TokenInput) -> UnverifiedClaims:
    """Decode a v#.public token's message and footer WITHOUT checking the
    signature, by splitting the message from the trailing signature using
    the version's fixed, public signature length. UNVERIFIED.

    A "local" token's payload is genuinely encrypted ciphertext - there is
    nothing meaningful to decode without the decryption key, so this returns
    a structured error for it rather than garbage bytes.
    """
    try:
        h = parse_token_header(input.token)
    except ValueError as err:
        return UnverifiedClaims(well_formed=False, error=str(err))

    if h.purpose != "public":
        return UnverifiedClaims(
            version=h.version,
            well_formed=True,
            error=(
                'this is a "local" (encrypted) token - its payload is ciphertext, not '
                "signed plaintext, so there is nothing to decode without the decryption "
                "key; use VerifyLocal instead"
            ),
        )

    try:
        payload_and_sig = base64url_decode(h.parts[2])
    except ValueError as err:
        return UnverifiedClaims(version=h.version, well_formed=False, error=f"payload segment is not valid base64url: {err}")

    sig_size = PUBLIC_SIG_SIZE[h.version]
    if len(payload_and_sig) <= sig_size:
        return UnverifiedClaims(
            version=h.version,
            well_formed=False,
            error=f"payload too short to contain a v{h.version} signature ({sig_size} bytes)",
        )
    message_bytes = payload_and_sig[: len(payload_and_sig) - sig_size]

    footer_json = ""
    if len(h.parts) == 4 and h.parts[3]:
        try:
            footer_bytes = base64url_decode(h.parts[3])
            footer_text = footer_bytes.decode("utf-8")
        except (ValueError, UnicodeDecodeError) as err:
            return UnverifiedClaims(version=h.version, well_formed=False, error=f"footer segment is invalid: {err}")
        parsed_json, is_json = try_json_loads(footer_text)
        # Always surface the footer as JSON text: the parsed object when it
        # is JSON, else the raw text as a JSON string literal.
        footer_json = parsed_json if is_json else json.dumps(footer_text)

    return UnverifiedClaims(
        version=h.version,
        claims_json=bytes_to_claims_json(message_bytes),
        footer_json=footer_json,
        well_formed=True,
        warning="UNVERIFIED - signature not checked; call VerifyPublic to authenticate",
    )
IdentifyTokenunary
TokenInput·TokenInfo

The IdentifyToken node analyzes a PASETO token to determine its version and purpose while verifying its structural integrity without engaging in any cryptographic operations. It ensures that the token adheres to the expected format and returns structured error information for invalid tokens.

View source
from gen.messages_pb2 import TokenInput, TokenInfo
from gen.axiom_context import AxiomContext

from nodes.paseto_util import parse_token_header


def identify_token(ax: AxiomContext, input: TokenInput) -> TokenInfo:
    """Identify a PASETO token's version and purpose, and check basic
    structural well-formedness, WITHOUT touching any cryptography.
    """
    try:
        h = parse_token_header(input.token)
    except ValueError as err:
        return TokenInfo(well_formed=False, error=str(err))

    return TokenInfo(
        version=h.version,
        purpose=h.purpose,
        header=h.header,
        part_count=len(h.parts),
        well_formed=True,
        footer_present=len(h.parts) == 4 and bool(h.parts[3]),
    )
InspectFooterunary
TokenInput·FooterInfo

The InspectFooter node decodes the footer of a PASETO token without requiring a key, allowing for inspection of the authenticated but unencrypted footer. It attempts to parse the footer as JSON and reports the result, falling back to the raw decoded text if parsing fails.

View source
from gen.messages_pb2 import TokenInput, FooterInfo
from gen.axiom_context import AxiomContext

from nodes.paseto_util import parse_token_header, base64url_decode, try_json_loads


def inspect_footer(ax: AxiomContext, input: TokenInput) -> FooterInfo:
    """Decode ONLY the footer of a PASETO token, WITHOUT needing any key -
    the footer is authenticated but never encrypted, so it is always
    inspectable.
    """
    try:
        h = parse_token_header(input.token)
    except ValueError as err:
        return FooterInfo(present=False, error=str(err))

    if len(h.parts) != 4 or not h.parts[3]:
        return FooterInfo(present=False)

    try:
        footer_bytes = base64url_decode(h.parts[3])
    except ValueError as err:
        return FooterInfo(present=True, error=f"footer segment is not valid base64url: {err}")

    try:
        footer_raw = footer_bytes.decode("utf-8")
    except UnicodeDecodeError as err:
        return FooterInfo(present=True, error=f"footer is not valid UTF-8: {err}")

    footer_json, is_json = try_json_loads(footer_raw)
    return FooterInfo(
        present=True,
        footer_raw=footer_raw,
        footer_json=footer_json,
        is_json=is_json,
    )
ParsePaserkunary
ParsePaserkInput·ParsePaserkResult

The ParsePaserk node structurally parses a PASERK-formatted key string to identify its PASETO version and key type, ensuring it decodes without error. It distinguishes between secret-carrying and public key types, returning relevant information while safeguarding sensitive key material.

View source
import base64

from gen.messages_pb2 import ParsePaserkInput, ParsePaserkResult
from gen.axiom_context import AxiomContext

from nodes.paseto_util import base64url_decode, PASERK_TYPES


def parse_paserk(ax: AxiomContext, input: ParsePaserkInput) -> ParsePaserkResult:
    """Structurally parse a PASERK-formatted key string to identify its
    PASETO version and key type, and confirm it decodes without error.
    Secret-carrying key types never have their key bytes echoed back.
    """
    paserk = input.paserk or ""
    parts = paserk.split(".")
    if len(parts) < 3:
        return ParsePaserkResult(well_formed=False, error=f"expected at least 3 dot-separated segments, found {len(parts)}")

    v_raw, key_type = parts[0], parts[1]
    if not v_raw.startswith("k") or not v_raw[1:].isdigit():
        return ParsePaserkResult(well_formed=False, error=f'malformed version segment "{v_raw}" - expected "k1".."k4"')
    version = int(v_raw[1:])
    if version not in (1, 2, 3, 4):
        return ParsePaserkResult(well_formed=False, error=f"unsupported PASERK version {version} - expected 1-4")

    info = PASERK_TYPES.get(key_type)
    if info is None:
        return ParsePaserkResult(
            version=version,
            well_formed=False,
            error=f'unrecognized PASERK key type "{key_type}" - expected one of {", ".join(sorted(PASERK_TYPES))}',
        )

    data_segments = parts[2:]
    if info["wrapped"]:
        # e.g. "k4.local-wrap.pie.<data>" - at least an algorithm prefix and a data blob.
        if len(data_segments) < 2 or not all(data_segments):
            return ParsePaserkResult(
                version=version,
                key_type=key_type,
                is_secret_material=info["secret"],
                well_formed=False,
                error=f'"{key_type}" requires a "<prefix>.<data>" tail',
            )
        # The wrapping/encryption payload itself is opaque without the wrapping
        # key/password, so only structural presence is checked here.
        return ParsePaserkResult(version=version, key_type=key_type, is_secret_material=info["secret"], well_formed=True)

    data = ".".join(data_segments)
    if not data:
        return ParsePaserkResult(
            version=version, key_type=key_type, is_secret_material=info["secret"], well_formed=False, error="empty data segment"
        )
    try:
        raw = base64url_decode(data)
    except ValueError as err:
        return ParsePaserkResult(
            version=version,
            key_type=key_type,
            is_secret_material=info["secret"],
            well_formed=False,
            error=f"data segment is not valid base64url: {err}",
        )

    public_material = base64.b64encode(raw).decode("ascii") if key_type == "public" else ""
    return ParsePaserkResult(
        version=version,
        key_type=key_type,
        is_secret_material=info["secret"],
        well_formed=True,
        public_key_material_b64=public_material,
    )
PaserkIdunary
PaserkIdInput·PaserkIdResult

The PaserkId node computes the PASERK ID(s) for a provided PASETO key using deterministic hashing methods, allowing for secure key rotation and lookup without revealing the key material. It also retrieves the peer ID for public-purpose secret keys.

View source
from pyseto import Key
from pyseto.exceptions import PysetoError

from gen.messages_pb2 import PaserkIdInput, PaserkIdResult
from gen.axiom_context import AxiomContext

from nodes.paseto_util import decode_key_material, VALID_VERSIONS, VALID_PURPOSES


def paserk_id(ax: AxiomContext, input: PaserkIdInput) -> PaserkIdResult:
    """Compute the PASERK ID(s) of a caller-supplied PASETO key - a
    deterministic SHA-384 (v1/v3) or BLAKE2b (v2/v4) hash-based fingerprint.
    For a "public"-purpose secret key, also returns the peer ID of its
    corresponding public key.
    """
    if input.version not in VALID_VERSIONS:
        return PaserkIdResult(error=f"version must be 1-4, got {input.version}")
    if input.purpose not in VALID_PURPOSES:
        return PaserkIdResult(error=f'purpose must be "local" or "public", got "{input.purpose}"')

    if input.purpose == "local":
        try:
            key_bytes = decode_key_material(input.key_material, input.key_encoding)
        except ValueError as err:
            return PaserkIdResult(error=str(err))
        key_arg = key_bytes
    else:
        pem = (input.key_material or "").strip()
        if not pem.startswith("-----BEGIN"):
            return PaserkIdResult(error='for purpose="public", key_material must be a PEM-encoded public or secret key')
        key_arg = pem

    try:
        key = Key.new(input.version, input.purpose, key_arg)
    except (ValueError, PysetoError) as err:
        return PaserkIdResult(error=f"invalid key material for v{input.version}.{input.purpose}: {err}")

    try:
        pid = key.to_paserk_id()
    except (ValueError, PysetoError, NotImplementedError) as err:
        return PaserkIdResult(error=f"could not compute PASERK ID: {err}")

    peer_id = ""
    if input.purpose == "public":
        try:
            peer_id = key.to_peer_paserk_id()
        except (ValueError, PysetoError, NotImplementedError):
            peer_id = ""

    return PaserkIdResult(paserk_id=pid, peer_paserk_id=peer_id)
ValidateClaimsunary
ValidateClaimsInput·ClaimsValidationResult

The ValidateClaims node validates registered claims (exp, nbf, iat, aud, iss, sub) in a claims JSON object against a caller-supplied current time and expected audience/issuer. It checks the content of the claims and returns a structured result indicating the validity of each claim and an overall validity status.

View source
import json
from datetime import timedelta

from gen.messages_pb2 import ValidateClaimsInput, ClaimsValidationResult, ClaimCheck
from gen.axiom_context import AxiomContext

from nodes.paseto_util import parse_registered_time


def validate_claims(ax: AxiomContext, input: ValidateClaimsInput) -> ClaimsValidationResult:
    """Validate the registered claims (exp/nbf/iat/aud/iss/sub) of a claims
    JSON object against a caller-supplied `now` (ISO 8601 - PASETO's own
    registered-claim time format, not JWT's unix-second NumericDate). Checks
    claim CONTENT only - combine with VerifyLocal/VerifyPublic to authenticate
    the token first.
    """
    if input.clock_skew_seconds < 0:
        return ClaimsValidationResult(error="clock_skew_seconds must be >= 0")

    try:
        claims = json.loads(input.claims_json)
    except (json.JSONDecodeError, TypeError) as err:
        return ClaimsValidationResult(error=f"claims_json is not valid JSON: {err}")
    if not isinstance(claims, dict):
        return ClaimsValidationResult(error="claims_json must decode to a JSON object")

    try:
        now = parse_registered_time(input.now)
    except ValueError as err:
        return ClaimsValidationResult(error=f"now: {err}")

    leeway = timedelta(seconds=input.clock_skew_seconds)

    def time_check(name: str) -> ClaimCheck:
        if name not in claims:
            return ClaimCheck(present=False, valid=True)
        try:
            t = parse_registered_time(str(claims[name]))
        except ValueError as err:
            return ClaimCheck(present=True, valid=False, detail=f"invalid {name}: {err}")
        if name == "exp":
            ok = now <= t + leeway
            detail = "" if ok else f"expired at {claims[name]}"
        elif name == "nbf":
            ok = now >= t - leeway
            detail = "" if ok else f"not valid until {claims[name]}"
        else:  # iat - informational only, no forward/backward rule
            ok = True
            detail = ""
        return ClaimCheck(present=True, valid=ok, detail=detail)

    def string_check(name: str, expected: str) -> ClaimCheck:
        present = name in claims
        if not expected:
            return ClaimCheck(present=present, valid=True)
        if not present:
            return ClaimCheck(present=False, valid=False, detail=f"{name} claim absent, expected {expected!r}")
        actual = claims[name]
        ok = actual == expected
        detail = "" if ok else f"{name} mismatch: got {actual!r}, expected {expected!r}"
        return ClaimCheck(present=True, valid=ok, detail=detail)

    exp = time_check("exp")
    nbf = time_check("nbf")
    iat = time_check("iat")
    aud = string_check("aud", input.expected_audience)
    iss = string_check("iss", input.expected_issuer)
    sub = ClaimCheck(present="sub" in claims, valid=True)

    all_valid = exp.valid and nbf.valid and iat.valid and aud.valid and iss.valid

    return ClaimsValidationResult(exp=exp, nbf=nbf, iat=iat, aud=aud, iss=iss, sub=sub, all_valid=all_valid)
VerifyLocalunary
VerifyLocalInput·VerifyResult

The VerifyLocal node cryptographically decrypts and authenticates a symmetric PASETO token using a caller-supplied key, without performing claim or time checks. It returns a result indicating the validity of the token along with any associated claims and footer data.

View source
import pyseto
from pyseto import Key
from pyseto.exceptions import PysetoError

from gen.messages_pb2 import VerifyLocalInput, VerifyResult
from gen.axiom_context import AxiomContext

from nodes.paseto_util import parse_token_header, decode_key_material, bytes_to_claims_json


def verify_local(ax: AxiomContext, input: VerifyLocalInput) -> VerifyResult:
    """Cryptographically decrypt and authenticate a v1-v4 "local" (symmetric)
    PASETO token against a caller-supplied key. Never checks claim content or
    the system clock - use ValidateClaims/CheckExpiration on the result.
    """
    try:
        h = parse_token_header(input.token)
    except ValueError as err:
        return VerifyResult(error=str(err))

    if h.purpose != "local":
        return VerifyResult(version=h.version, error='this is a "public" token; use VerifyPublic instead')

    implicit_assertion = (input.implicit_assertion or "").encode("utf-8")
    if h.version in (1, 2) and implicit_assertion:
        return VerifyResult(
            version=h.version,
            error=f"v{h.version} tokens do not support an implicit assertion (v3/v4 only); "
            "the platform would silently ignore it, so this is rejected explicitly rather than "
            "misleading the caller into thinking it was checked",
        )

    try:
        key_bytes = decode_key_material(input.key_material, input.key_encoding)
    except ValueError as err:
        return VerifyResult(version=h.version, error=str(err))

    try:
        key = Key.new(h.version, "local", key_bytes)
    except (ValueError, PysetoError) as err:
        return VerifyResult(version=h.version, error=f"invalid key material for v{h.version}.local: {err}")

    try:
        token = pyseto.decode(key, input.token, implicit_assertion=implicit_assertion, deserializer=None)
    except (ValueError, PysetoError) as err:
        return VerifyResult(version=h.version, valid=False, reason=str(err))
    except Exception as err:  # noqa: BLE001 - never leak a raw traceback for attacker-controlled input
        return VerifyResult(version=h.version, valid=False, reason=f"decryption/authentication failed: {err}")

    footer_json = ""
    if token.footer:
        footer_bytes = token.footer if isinstance(token.footer, bytes) else str(token.footer).encode("utf-8")
        footer_json = bytes_to_claims_json(footer_bytes)

    payload_bytes = token.payload if isinstance(token.payload, bytes) else str(token.payload).encode("utf-8")
    return VerifyResult(
        valid=True,
        version=h.version,
        claims_json=bytes_to_claims_json(payload_bytes),
        footer_json=footer_json,
        reason="verified",
    )
VerifyPublicunary
VerifyPublicInput·VerifyResult

The VerifyPublic node cryptographically verifies the signature of a v1-v4 'public' PASETO token using a caller-supplied PEM public key, ensuring the integrity of the token without performing claim or time checks. It returns a verification result indicating whether the signature is valid or not, along with any relevant error messages.

View source
import pyseto
from pyseto import Key
from pyseto.exceptions import PysetoError

from gen.messages_pb2 import VerifyPublicInput, VerifyResult
from gen.axiom_context import AxiomContext

from nodes.paseto_util import parse_token_header, bytes_to_claims_json


def verify_public(ax: AxiomContext, input: VerifyPublicInput) -> VerifyResult:
    """Cryptographically verify the signature of a v1-v4 "public" (asymmetric)
    PASETO token against a caller-supplied PEM public key. Never checks claim
    content or the system clock - use ValidateClaims/CheckExpiration on the result.
    """
    try:
        h = parse_token_header(input.token)
    except ValueError as err:
        return VerifyResult(error=str(err))

    if h.purpose != "public":
        return VerifyResult(version=h.version, error='this is a "local" token; use VerifyLocal instead')

    implicit_assertion = (input.implicit_assertion or "").encode("utf-8")
    if h.version in (1, 2) and implicit_assertion:
        return VerifyResult(
            version=h.version,
            error=f"v{h.version} tokens do not support an implicit assertion (v3/v4 only); "
            "the platform would silently ignore it, so this is rejected explicitly rather than "
            "misleading the caller into thinking it was checked",
        )

    pem = (input.public_key_pem or "").strip()
    if not pem.startswith("-----BEGIN"):
        return VerifyResult(version=h.version, error="public_key_pem must be a PEM-encoded key (missing -----BEGIN header)")

    try:
        key = Key.new(h.version, "public", pem)
    except (ValueError, PysetoError) as err:
        return VerifyResult(version=h.version, error=f"invalid public key material for v{h.version}.public: {err}")

    try:
        token = pyseto.decode(key, input.token, implicit_assertion=implicit_assertion, deserializer=None)
    except (ValueError, PysetoError) as err:
        return VerifyResult(version=h.version, valid=False, reason=str(err))
    except Exception as err:  # noqa: BLE001 - never leak a raw traceback for attacker-controlled input
        return VerifyResult(version=h.version, valid=False, reason=f"signature verification failed: {err}")

    footer_json = ""
    if token.footer:
        footer_bytes = token.footer if isinstance(token.footer, bytes) else str(token.footer).encode("utf-8")
        footer_json = bytes_to_claims_json(footer_bytes)

    payload_bytes = token.payload if isinstance(token.payload, bytes) else str(token.payload).encode("utf-8")
    return VerifyResult(
        valid=True,
        version=h.version,
        claims_json=bytes_to_claims_json(payload_bytes),
        footer_json=footer_json,
        reason="verified",
    )

Messages (16)

Download .proto
CheckExpirationInput
claims_json:string

A JSON string representing the claims object to be checked for expiration.

now:string

An ISO 8601/RFC 3339 formatted string representing the current time against which the expiration is checked.

ClaimCheck
present:bool
valid:bool
detail:string
ClaimsValidationResult
exp:ClaimCheck
nbf:ClaimCheck
iat:ClaimCheck
aud:ClaimCheck
iss:ClaimCheck
sub:ClaimCheck
all_valid:bool
error:string
ExpirationResult
exp_present:bool
expired:bool
exp:string
seconds_remaining:int64
error:string
FooterInfo
present:bool

Indicates whether the footer is present and successfully decoded.

footer_raw:string

The raw decoded text of the footer segment.

footer_json:string

The parsed JSON object from the footer if the JSON parse was successful.

is_json:bool

A boolean indicating whether the footer was successfully parsed as JSON.

error:string

Contains any error messages encountered during the decoding process.

ParsePaserkInput
paserk:string

The PASERK-formatted key string to be parsed.

ParsePaserkResult
version:int32

The PASETO version extracted from the PASERK string.

key_type:string

The type of key identified from the PASERK string.

PaserkIdInput
key_material:string

Contains the actual key data, which should be in a specific format depending on the purpose.

key_encoding:string

Defines the encoding format of the key material, necessary for decoding the key correctly.

version:int32

Specifies the version of the PASETO protocol being used, which must be between 1 and 4.

purpose:string

Indicates the intended use of the key, which can be either 'local' or 'public'.

PaserkIdResult
paserk_id:string
peer_paserk_id:string
error:string
TokenInfo
version:int32

The version of the PASETO token, indicating which version (1-4) it conforms to.

purpose:string

The purpose of the PASETO token, which can be either 'local' or 'public'.

header:string

The header segment of the PASETO token, providing metadata about the token.

part_count:int32

The number of segments (parts) in the PASETO token.

well_formed:bool

A boolean indicating whether the token is structurally well-formed.

footer_present:bool

A boolean indicating whether the footer segment is present in the token.

error:string

A string containing error details if the token is not well-formed.

TokenInput
token:string

The input token that contains the message and signature to be decoded.

UnverifiedClaims
version:int32

The version of the token being processed, indicating the signature algorithm used.

claims_json:string

The decoded claims from the token's message, represented in JSON format.

footer_json:string

The decoded footer from the token, represented in JSON format or as a string literal if not valid JSON.

well_formed:bool

A boolean indicating whether the token was well-formed and successfully decoded.

warning:string

A warning indicating that the signature has not been verified and the token is unverified.

error:string

A structured error message detailing any issues encountered during the decoding process.

ValidateClaimsInput
claims_json:string

A JSON string containing the claims to be validated.

now:string

An ISO 8601 string representing the current time against which claims are validated.

expected_audience:string

The expected audience value that the 'aud' claim should match.

expected_issuer:string

The expected issuer value that the 'iss' claim should match.

clock_skew_seconds:int64

A non-negative integer representing the allowable clock skew in seconds for time-based claims.

VerifyLocalInput
token:string

The PASETO token to be verified.

key_material:string

The key material used for decryption, which must be provided by the caller.

key_encoding:string

The encoding format of the key material, which can be utf8, hex, base64, or base64url.

implicit_assertion:string

An optional assertion that must match the one used to create the token, applicable only for v3/v4 tokens.

VerifyPublicInput
token:string

The PASETO token to be verified.

public_key_pem:string

The PEM-encoded public key used for signature verification.

implicit_assertion:string

An optional assertion for v3/v4 tokens that can be used during verification.

VerifyResult
valid:bool
version:int32
claims_json:string
footer_json:string
reason:string
error:string

Use as a tool in any AI agent

christiangeorgelucas/paseto-tools is callable as a tool from any MCP client (Claude, Cursor, or your own agent) via Axiom's hosted MCP server — no install, no download. Add the server, then your agent can search and invoke it, or pin it as a typed tool.

claude mcp add --transport http axiom https://api.axiomide.com/mcp --header "Authorization: Bearer YOUR_AXIOM_API_KEY"

Replace YOUR_AXIOM_API_KEY with a key you create in Console → API Keys.

MCP server setup & other clients →

Use this package

Sign in to import christiangeorgelucas/paseto-tools into the graph editor — drop it into a flow and hit run. Or call it directly via the API Reference with your API key.

Sign in to useOpen editor

Stats

Error rate (1h)0.0%
Back to marketplace