stano-security 0.1.0

JWT encode/decode (ES256) and generic SecurityContext<E> for auth on the Stano platform
Documentation

stano-security

JWT-based authentication primitives: encode/decode generically-extensible JWT claims and wrap them in a security context for task-local request identity.

Install

[dependencies]
stano-security = { path = "../stano-security" }

API

JWT Configuration & Functions

  • JwtConfig { private_key_pem, public_key_pem, expiration_seconds } — holds EC private/public key PEM strings and an expiration duration in seconds (stored on the config; not automatically enforced at encode time — see Notes). Keys must be EC (prime256v1) for ES256 signing.

  • encode_jwt(claims: &Claims<E>, config: &JwtConfig) -> Result<String, JwtError> — encodes Claims into a signed JWT token (ES256 algorithm).

  • decode_jwt(token: &str, config: &JwtConfig) -> Result<Claims<E>, JwtError> — decodes and verifies a JWT token, returning the claims. Validates signature and expiration.

  • JwtError — error type:

    • EncodingFailed(String) — failed to encode token.
    • DecodingFailed(String) — failed to decode token.
    • InvalidKey(String) — PEM key format invalid.
    • TokenExpired — token's exp timestamp is in the past.
    • InvalidToken(String) — token is malformed or otherwise invalid.

Claims & Context

  • Claims<E> { sub: String, session_id: String, exp: usize, ext: E } — generic JWT payload.

    • sub — typically a user/app typed ID (as a string).
    • session_id — arbitrary session tracking ID.
    • exp — expiration time (UTC timestamp, seconds since epoch).
    • ext — app-defined extension type (flattened in JSON; use for email, role, custom claims, etc.).
  • SecurityContext<E> — wraps Claims<E> for use in request handlers.

    • new(claims: Claims<E>) -> Self
    • sub(&self) -> &str — extract subject.
    • session_id(&self) -> &str — extract session ID.
    • ext(&self) -> &E — extract app extensions.
    • claims(&self) -> &Claims<E> — access full claims struct.

Usage Example

use stano_security::{JwtConfig, Claims, SecurityContext, encode_jwt, decode_jwt};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct MyExt {
    email: String,
}

// Setup keys (from environment or file).
let config = JwtConfig {
    private_key_pem: "-----BEGIN EC PRIVATE KEY-----\n...".into(),
    public_key_pem: "-----BEGIN PUBLIC KEY-----\n...".into(),
    expiration_seconds: 3600,
};

// Create claims (app fills in `exp` based on desired lifetime).
let claims = Claims {
    sub: "user-123".into(),
    session_id: "session-456".into(),
    exp: (std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() + 3600) as usize,
    ext: MyExt { email: "user@example.com".into() },
};

// Encode to JWT string.
let token = encode_jwt(&claims, &config)?;

// Decode and verify (in a request handler).
let decoded: Claims<MyExt> = decode_jwt(&token, &config)?;
let ctx = SecurityContext::new(decoded);

println!("User: {}", ctx.sub());
println!("Email: {}", ctx.ext().email);

Notes

  • App-defined extension typeE is your crate's type. Define an enum or struct containing role, permissions, email, or any other custom claims; it gets flattened into the JWT JSON.
  • No authorization — this crate only handles JWT encode/decode. Authorization (role checks, permission guards) are the app's responsibility.
  • Keys must be EC (prime256v1) for ES256 signing. Generate with openssl ecparam -name prime256v1 -genkey -noout -out private.pem and openssl ec -in private.pem -pubout -out public.pem.
  • Token expiration — the expiration_seconds field in JwtConfig is stored but not automatically used by encode/decode. Callers must set Claims.exp themselves (typically now + expiration_seconds).