rusty_paseto 0.10.0

A type-driven, ergonomic alternative to JWT for secure stateless PASETO tokens.
Documentation
export const metadata = {
  title: 'Parsing Tokens',
  description:
    'Learn how to parse and decrypt PASETO tokens using PasetoParser.',
}

# Parsing Tokens

The `PasetoParser` decrypts or verifies PASETO tokens and provides automatic claim validation. {{ className: 'lead' }}

## Basic Parsing

Parse a token with the default parser:

```rust
use rusty_paseto::prelude::*;

let payload = PasetoParser::<V4, Local>::default()
    .parse(&token, &key)?;

println!("Payload: {}", payload);
// {"sub":"user_123","exp":"2025-01-01T00:00:00Z",...}
```

The parser automatically:
- Decrypts (Local) or verifies (Public) the token
- Validates `exp` (expiration) claim
- Validates `nbf` (not before) claim

---

## Constructor Semantics

Understanding when to use `default()` vs `new()`:

| Constructor | Behavior |
|-------------|----------|
| `PasetoParser::default()` | Auto-validates `exp` and `nbf` claims |
| `PasetoParser::new()` | Skips automatic time validation |

Use `default()` for most applications. Use `new()` only when you need custom time validation logic or want to skip time-based validation entirely.

```rust
use rusty_paseto::prelude::*;

// Recommended: automatic time validation
let payload = PasetoParser::<V4, Local>::default()
    .parse(&token, &key)?;

// Skip automatic time validation (use with caution)
let payload = PasetoParser::<V4, Local>::new()
    .parse(&token, &key)?;
```

---

## Public Token Verification

For Public purpose tokens, provide the public key:

```rust
use rusty_paseto::prelude::*;

let public_key = PasetoAsymmetricPublicKey::<V4, Public>::from(&key_bytes);

let payload = PasetoParser::<V4, Public>::default()
    .parse(&token, &public_key)?;
```

---

## Expecting Claims

Use `check_claim` to verify specific claim values:

```rust
use rusty_paseto::prelude::*;

let payload = PasetoParser::<V4, Local>::default()
    .check_claim(SubjectClaim::from("user_123"))
    .check_claim(AudienceClaim::from("my-app"))
    .check_claim(IssuerClaim::from("auth.example.com"))
    .parse(&token, &key)?;
```

If any claim doesn't match, parsing returns `Error::InvalidClaimValue`.

### Fluent Expect Methods

For convenience, use the `expect_*` methods:

```rust
let payload = PasetoParser::<V4, Local>::default()
    .expect_subject("user_123")
    .expect_audience("my-app")
    .expect_issuer("auth.example.com")
    .parse(&token, &key)?;
```

---

## Requiring Claims

Ensure claims exist without checking specific values:

```rust
use rusty_paseto::prelude::*;

let payload = PasetoParser::<V4, Local>::default()
    .validate_claim("role", |_| true)  // Just check existence
    .parse(&token, &key)?;
```

---

## Footer Validation

If the token has a footer, you can require it matches:

```rust
use rusty_paseto::prelude::*;

let payload = PasetoParser::<V4, Local>::default()
    .set_footer(Footer::from(r#"{"kid":"key-v1"}"#))
    .parse(&token, &key)?;
```

If the footer doesn't match, parsing returns `Error::FooterMismatch`.

---

## Implicit Assertions

Provide implicit assertions that were used during token creation:

```rust
use rusty_paseto::prelude::*;

let payload = PasetoParser::<V4, Local>::default()
    .set_implicit_assertion(ImplicitAssertion::from("tenant:acme"))
    .parse(&token, &key)?;
```

---

## Error Handling

Handle common parsing errors:

```rust
use rusty_paseto::{prelude::*, Error};

let result = PasetoParser::<V4, Local>::default()
    .check_claim(AudienceClaim::from("my-app"))
    .parse(&token, &key);

match result {
    Ok(payload) => {
        println!("Valid token: {}", payload);
    }
    Err(Error::Expired) => {
        println!("Token has expired - please re-authenticate");
    }
    Err(Error::NotYetValid(time)) => {
        println!("Token not valid until: {}", time);
    }
    Err(Error::InvalidClaimValue { claim, expected, actual }) => {
        println!("Claim {} mismatch: expected {}, got {}", claim, expected, actual);
    }
    Err(Error::FooterMismatch) => {
        println!("Token footer doesn't match expected value");
    }
    Err(Error::InvalidSignature) => {
        println!("Token signature invalid - possible tampering");
    }
    Err(e) => {
        println!("Parse error: {}", e);
    }
}
```

---

## Accessing Payload Data

### Recommended: Typed Parsing

For production code, use `parse_into<T>()` to deserialize directly into a struct:

```rust
use rusty_paseto::prelude::*;
use serde::Deserialize;

#[derive(Deserialize)]
struct MyClaims {
    sub: String,
    #[serde(default)]
    role: Option<String>,
}

let claims: MyClaims = PasetoParser::<V4, Local>::default()
    .parse_into(&token, &key)?;

println!("Subject: {}, Role: {:?}", claims.sub, claims.role);
```

This provides compile-time type safety and cleaner code. See [Typed Parsing](/typed-parsing) for more patterns.

### Dynamic JSON Access

For dynamic access or when claim structure varies:

```rust
use rusty_paseto::prelude::*;

let payload = PasetoParser::<V4, Local>::default()
    .parse(&token, &key)?;

// Access claims dynamically
let sub = payload["sub"].as_str().unwrap();
let role = payload["role"].as_str().unwrap_or("user");

println!("Subject: {}, Role: {}", sub, role);
```