PASETO
PASETO v4 tokens, the safer JWT: v4.local encrypted with a shared key and v4.public signed with Ed25519, expiry checked.
Version 1.0.0 · 6 functions · 2 crates
Functions
What use paseto.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use paseto_local_key, paseto_encrypt from paseto (Quillo 0.6).
paseto_local_key() → stror an errorA new key for v4.local tokens: 32 random bytes as base64. Everyone who has it can make and read tokens.
paseto_encrypt(claims_map: any, key: str, expires_in: int) → stror an errorA v4.local. token holding claims encrypted with key: nobody without the key can read or change them. iat and nbf are set to now; exp to now plus expires_in seconds (0: never expires). iss, sub, aud, jti are text.
paseto_decrypt(token: str, key: str) → anyor an errorThe claims of a v4.local token made with key; an error for another key, a changed token, or one that has expired.
paseto_keypair() → mapor an errorA new Ed25519 key pair for v4.public tokens: { secret, public }, base64 (64 and 32 bytes). Sign with secret; give public to the services that check.
paseto_sign(claims_map: any, secret: str, expires_in: int) → stror an errorA v4.public. token: claims readable by anyone, signed with secret; exp as paseto_encrypt sets it.
paseto_verify(token: str, public_key: str) → anyor an errorThe claims of a v4.public token, checked with public_key; an error for a wrong signature or an expired token.
Get it
$ quillo install paseto # paseto.qwer into this folder$ quillo install paseto --example # and its example, paseto.qweOr with curl:
$ curl -O https://quillolang.org/plugins/files/paseto.qwer$ curl -O https://quillolang.org/plugins/files/paseto.qweThen, in your program, all of it or only what you call:
use paseto.qweruse paseto_local_key, paseto_encrypt from paseto // Quillo 0.6: only theseIt uses these crates, which cargo fetches the first time the plugin is built:
pasetors = "0.7"base64 = "0.22"Example
// Sign-in tokens with PASETO: an encrypted session token, and a signed one other services check.//// quillo run paseto.qwe then open http://localhost:8080/sessionuse "paseto.qwer"let LOCAL_KEY = env("PASETO_KEY") ?? paseto_local_key()?let SIGNING = paseto_keypair()?route GET /session { let token = paseto_encrypt({ sub: "user-7", role: "admin", cart: [12, 31] }, LOCAL_KEY, 3600)? let claims = paseto_decrypt(token, LOCAL_KEY)? { token, user: claims.sub, role: claims.role, cart: claims.cart, other_key: is_err(paseto_decrypt(token, paseto_local_key()?)) }}route GET /public { let token = paseto_sign({ sub: "service-billing", aud: "orders-api", scope: "orders:read" }, SIGNING.secret, 300)? let claims = paseto_verify(token, SIGNING.public)? { prefix: slice(token, 0, 10), service: claims.sub, scope: claims.scope, expires: claims.exp, forged: is_err(paseto_verify(token, paseto_keypair()?.public)) }}$ quillo run paseto.qwe$ curl 'localhost:8080/session'$ curl 'localhost:8080/public'Source
//! PASETO v4 tokens, the safer JWT: `v4.local` encrypted with a shared key and `v4.public` signed with Ed25519, with expiry checked, for sessions and APIs.//! version: 1.0.0//!//! [dependencies]//! pasetors = "0.7"//! base64 = "0.22"use base64::engine::general_purpose::STANDARD;use base64::Engine;use pasetors::claims::{Claims, ClaimsValidationRules};use pasetors::keys::{AsymmetricKeyPair, AsymmetricPublicKey, AsymmetricSecretKey, Generate, SymmetricKey};use pasetors::token::UntrustedToken;use pasetors::version4::V4;use pasetors::{local, public, Local, Public};use serde_json::Value;use std::collections::HashMap;fn key_bytes(text: &str, len: usize, what: &str) -> Result<Vec<u8>, String> { let b = STANDARD.decode(text.trim()).map_err(|_| format!("the {} is not base64", what))?; if b.len() != len { return Err(format!("the {} is {} bytes, not {}", what, len, b.len())); } Ok(b)}fn claims(values: Value, expires_in: i64) -> Result<Claims, String> { let Value::Object(map) = values else { return Err("the claims are a map".into()) }; let mut c = Claims::new().map_err(|e| e.to_string())?; if expires_in > 0 { c.set_expires_in(&std::time::Duration::from_secs(expires_in.min(10 * 365 * 86400) as u64)).map_err(|e| e.to_string())?; } else { c.non_expiring(); } for (k, v) in map { let text = || v.as_str().map(str::to_string).ok_or_else(|| format!("`{}` is text", k)); let r = match k.as_str() { "iss" => c.issuer(&text()?), "sub" => c.subject(&text()?), "aud" => c.audience(&text()?), "jti" => c.token_identifier(&text()?), "exp" => c.expiration(&text()?), "nbf" => c.not_before(&text()?), "iat" => c.issued_at(&text()?), _ => c.add_additional(&k, v.clone()), }; r.map_err(|e| format!("claim `{}`: {}", k, e))?; } Ok(c)}fn rules() -> ClaimsValidationRules { let mut r = ClaimsValidationRules::new(); r.allow_non_expiring(); r}fn payload(c: Option<&Claims>) -> Result<Value, String> { let c = c.ok_or("the token has no claims")?; serde_json::from_str(&c.to_string().map_err(|e| e.to_string())?).map_err(|e| e.to_string())}/// A new key for v4.local tokens: 32 random bytes as base64. Everyone who has it can make and read tokens.pub fn paseto_local_key() -> Result<String, String> { Ok(STANDARD.encode(SymmetricKey::<V4>::generate().map_err(|e| e.to_string())?.as_bytes()))}/// A `v4.local.` token holding `claims` encrypted with `key`: nobody without the key can read or change them. `iat` and `nbf`/// are set to now; `exp` to now plus `expires_in` seconds (0: never expires). `iss`, `sub`, `aud`, `jti` are text.pub fn paseto_encrypt(claims_map: serde_json::Value, key: &str, expires_in: i64) -> Result<String, String> { let k = SymmetricKey::<V4>::from(&key_bytes(key, 32, "key")?).map_err(|e| e.to_string())?; local::encrypt(&k, &claims(claims_map, expires_in)?, None, None).map_err(|e| e.to_string())}/// The claims of a v4.local token made with `key`; an error for another key, a changed token, or one that has expired.pub fn paseto_decrypt(token: &str, key: &str) -> Result<Value, String> { let k = SymmetricKey::<V4>::from(&key_bytes(key, 32, "key")?).map_err(|e| e.to_string())?; let t = UntrustedToken::<Local, V4>::try_from(token.trim()).map_err(|e| format!("not a v4.local token: {}", e))?; let trusted = local::decrypt(&k, &t, &rules(), None, None).map_err(|e| format!("{}: wrong key, changed or expired", e))?; payload(trusted.payload_claims())}/// A new Ed25519 key pair for v4.public tokens: `{ secret, public }`, base64 (64 and 32 bytes). Sign with `secret`; give/// `public` to the services that check.pub fn paseto_keypair() -> Result<HashMap<String, String>, String> { let kp = AsymmetricKeyPair::<V4>::generate().map_err(|e| e.to_string())?; Ok(HashMap::from([ ("secret".to_string(), STANDARD.encode(kp.secret.as_bytes())), ("public".to_string(), STANDARD.encode(kp.public.as_bytes())), ]))}/// A `v4.public.` token: `claims` readable by anyone, signed with `secret`; `exp` as paseto_encrypt sets it.pub fn paseto_sign(claims_map: serde_json::Value, secret: &str, expires_in: i64) -> Result<String, String> { let k = AsymmetricSecretKey::<V4>::from(&key_bytes(secret, 64, "secret key")?).map_err(|e| e.to_string())?; public::sign(&k, &claims(claims_map, expires_in)?, None, None).map_err(|e| e.to_string())}/// The claims of a v4.public token, checked with `public_key`; an error for a wrong signature or an expired token.pub fn paseto_verify(token: &str, public_key: &str) -> Result<Value, String> { let k = AsymmetricPublicKey::<V4>::from(&key_bytes(public_key, 32, "public key")?).map_err(|e| e.to_string())?; let t = UntrustedToken::<Public, V4>::try_from(token.trim()).map_err(|e| format!("not a v4.public token: {}", e))?; let trusted = public::verify(&k, &t, &rules(), None, None).map_err(|e| format!("{}: wrong key, changed or expired", e))?; payload(trusted.payload_claims())}Related
biscuit.qwer · 1.0.0Biscuit authorization tokens: issued with Datalog facts and checks signed by your key, narrowed by their holder without asking the issuer, and authorized offline against each request.7 views · 8 downloadsJWKS jwks.qwer · 1.0.0Verify sign-in tokens from Auth0, Okta, Entra ID, Cognito or Keycloak against the provider's JWKS keys, picked by kid.8 views · 6 downloadsJWT jwt.qwer · 2.0.0JSON Web Tokens: sign and verify HS256 tokens for an API, check a token's audience and issuer, verify RS256, ES256 and EdDSA tokens against an identity provider's JWKS, peek at claims.7 views · 8 downloadsCSRF tokens csrf_tokens.qwer · 1.0.0CSRF protection for forms and fetch calls: tokens signed for one session that expire, the signed double-submit cookie check, and the Origin, Referer and Sec-Fetch-Site checks.7 views · 7 downloadsRate limits rate_limit.qwer · 1.0.0Rate limits per key kept in memory (GCRA): allowed or retry after so many milliseconds, for sign-ins and APIs.7 views · 6 downloadsArgon2 passwords argon2.qwer · 1.0.0Argon2id password hashes with the memory and passes you choose, any Argon2 hash verified, its parameters read, old ones marked for a rehash, and the cost timed on this server.7 views · 6 downloadsThread 0 posts
On PASETO: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.