Overview of standard PASETO claims
mainrusty_paseto provides type-safe wrappers for each of these claims to ensure data integrity and ease of use during token construction and parsing.repository·main·Indexed 40 days ago
https://github.com/rrrodzilla/rusty_pasetoA type-driven, ergonomic Rust implementation of the PASETO (Platform-Agnostic Security Tokens) protocol for creating and verifying secure stateless tokens. Version 0.10.1 provides a three-layer architecture (batteries_included, generic, and core) to balance functionality and binary size, supporting various PASETO versions and purposes including V4 local and public tokens. It includes support for PASERK key serialization, implicit assertions, and custom claim validation.
rusty_paseto provides type-safe wrappers for each of these claims to ensure data integrity and ease of use during token construction and parsing.PASETO versions and purposes (Local vs. Public) determine the required key types and lengths. Use the following mapping to select the correct key for your implementation:
| Version | Local (Symmetric) | Public (Asymmetric) |
|---|---|---|
| V4 | 32-byte key | Ed25519 keypair |
| V3 | 32-byte key | P-384 ECDSA keypair |
| V2 | 32-byte key | Ed25519 keypair |
| V1 | 32-byte key | RSA keypair |
Footers are unencrypted, authenticated (tamper-proof) data appended to a PASETO token. They are visible without decryption and are ideal for storing metadata like Key IDs (kid) to facilitate key rotation.
When using footers, you can extract the footer from a token string using Footer::try_from_token before performing full decryption. This allows you to inspect the metadata and select the appropriate key for parsing.
use rusty_paseto::prelude::*;
let token = PasetoBuilder::<V4, Local>::default()
.set_claim(SubjectClaim::from("user_123"))
.set_footer(Footer::from(r#"{\"kid\":\"key-2024-01\"}"#))
.build(&key)?;rusty_paseto is organized into three distinct layers of abstraction. You should choose a layer based on the level of control and ease of use required for your application:
PasetoBuilder and PasetoParser and is the recommended starting point for most applications because it provides automatic claim validation and a fluent builder pattern.Layer Dependencies:
prelude feature automatically enables generic and core.To avoid duplicating common fields (like sub, aud, iss, exp), you can use a generic struct with #[serde(flatten)]. This allows you to define a base set of claims and inject application-specific custom claims into the same object.
use serde::Deserialize;
// Base claims present in all tokens
#[derive(Deserialize)]
struct BaseClaims<T> {
sub: String,
aud: String,
iss: String,
exp: String,
#[serde(flatten)]
custom: T,
}
// Application-specific custom claims
#[derive(Deserialize)]
struct AppCustomClaims {
role: String,
tenant_id: String,
}
// Usage
let claims: BaseClaims<AppCustomClaims> = PasetoParser::<V4, Local>::default()
.parse_into(&token, &key)?;
println!("Tenant: {}", claims.custom.tenant_id);The rusty_paseto crate is organized into three hierarchical architectural layers. You can choose a layer based on your need for features versus dependency weight:
batteries_included: The outermost layer. It includes JWT-style claims (like exp, iat, nbf) and built-in business rules (e.g., default 1-hour expiration). This is recommended for most users.generic: An intermediate layer for advanced users. It provides a generic builder and parser with claims that you can extend or customize (e.g., using different time crates or custom default business rules).core: The most basic layer. It provides only the essential PASETO cryptographic functions (encrypt/sign and decrypt/verify) for a payload, optional footer, and optional implicit assertion. It does not include claims, business rules, or serde dependencies, making it extremely lightweight.All layers use a common crypto core that varies depending on the PASETO version and purpose you select via feature gates.
The crate is organized into three layers. You should choose the layer that best fits your needs to balance functionality against binary size and dependency weight.
This is the most common layer. It includes JWT-style claims and business rules (e.g., customizable expiration, issued_at, not-before times).
features = ["batteries_included", "v4_local"]).use rusty_paseto::prelude::*;Allows you to create custom versions of the batteries_included layer. It provides a generic builder and parser along with claims you can extend.
serde_json::Value.features = ["generic", "v4_local"]).use rusty_paseto::generic::*;The most basic implementation. It handles raw PASETO crypto (Payload, optional Footer, and optional Implicit Assertion for V3/V4) using the appropriate key.
features = ["core", "v4_local"]).Implicit assertions are data cryptographically bound to the token but are not included in the token string itself. They must be provided by the caller during both token creation and token parsing.
If the assertion provided during parsing does not match the one used during creation, parsing will fail with Error::CryptoError. This is useful for multi-tenant systems (binding a token to a tenant ID) or request binding (binding to a specific API endpoint) without exposing the binding data in the token string.
use rusty_paseto::prelude::*;
// Setting an implicit assertion during creation
let token = PasetoBuilder::<V4, Local>::default()
.set_claim(SubjectClaim::from("user_123"))
.set_implicit_assertion(ImplicitAssertion::from("tenant:acme-corp"))
.build(&key)?;
// Parsing with the same implicit assertion
let payload = PasetoParser::<V4, Local>::default()
.set_implicit_assertion(ImplicitAssertion::from("tenant:acme-corp"))
.parse(&token, &key)?;New projects should use V4 as it offers the best security and performance with modern cryptographic algorithms.
| Version | Local Encryption | Public Signing | Notes |
|---|---|---|---|
| V1 | AES-256-CTR | RSA-PSS | Legacy, V1 Public deprecated |
| V2 | XChaCha20 | Ed25519 | Good, but V4 preferred |
| V3 | AES-256-CTR | P-384 ECDSA | NIST compliance |
| V4 | XChaCha20 + BLAKE2b | Ed25519 | Recommended |
Features in rusty_paseto follow a hierarchical dependency chain. Enabling a high-level feature automatically enables its required lower-level dependencies:
prelude $\rightarrow$ enables generic $\rightarrow$ enables coregeneric $\rightarrow$ enables corev4_local) $\rightarrow$ enables coreFor example, configuring features = ["v4_local", "prelude"] will actually enable v4_local, prelude, generic, and core.
The PasetoParser automatically validates standard time-based claims during the .parse() process. If these checks fail, the parser returns a specific error:
exp (Expiration): If the token's expiration time is in the past, it returns Error::Expired.nbf (Not Before): If the token is used before its not-before time, it returns Error::NotYetValid.use rusty_paseto::prelude::*;
// Automatic validation happens during parse
let payload = PasetoParser::<V4, Local>::default()
.parse(&token, &key)?;
// If exp is in the past -> Error::Expired
// If nbf is in the future -> Error::NotYetValidAfter parsing a token, you can access custom claims in two ways:
serde_json::Value and access keys using JSON indexing..parse_into::<T>() to deserialize the payload directly into a custom Rust struct that implements serde::Deserialize.// Method 1: Manual JSON Access
let payload = PasetoParser::<V4, Local>::default().parse(&token, &key)?;
let claims: serde_json::Value = serde_json::from_str(&payload)?;
let role = claims["role"].as_str().unwrap_or("unknown");
// Method 2: Typed Parsing
#[derive(Deserialize)]
struct MyClaims {
sub: String,
role: String,
level: i32,
#[serde(default)]
permissions: Vec<String>,
}
let claims: MyClaims = PasetoParser::<V4, Local>::default()
.parse_into(&token, &key)?;