rusty_paseto 0.10.0

A type-driven, ergonomic alternative to JWT for secure stateless PASETO tokens.
Documentation
export const metadata = {
  title: 'Building Tokens',
  description:
    'Learn how to create PASETO tokens using the fluent PasetoBuilder API.',
}

# Building Tokens

The `PasetoBuilder` provides a fluent API for creating PASETO tokens with claims, footers, and implicit assertions. {{ className: 'lead' }}

## Default Behavior

`PasetoBuilder::default()` automatically includes security-conscious defaults:

- **exp** (expiration): Set to 1 hour from now
- **iat** (issued at): Set to current time
- **nbf** (not before): Set to current time

This ensures tokens are secure by default. Override individual claims as needed, or use `set_no_expiration_danger_acknowledged()` to create non-expiring tokens.

---

## Basic Token Creation

Create a simple token with the builder:

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

let key = PasetoSymmetricKey::<V4, Local>::from(Key::try_new_random()?);

let token = PasetoBuilder::<V4, Local>::default()
    .set_claim(SubjectClaim::from("user_123"))
    .build(&key)?;

println!("{}", token);
// v4.local.encoded-encrypted-payload...
```

---

## Adding Claims

Use `set_claim()` to add standard or custom claims:

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

let token = PasetoBuilder::<V4, Local>::default()
    // Standard claims
    .set_claim(SubjectClaim::from("user_123"))
    .set_claim(AudienceClaim::from("my-app"))
    .set_claim(IssuerClaim::from("auth-service"))
    .set_claim(ExpirationClaim::try_from("2025-01-01T00:00:00Z")?)

    // Custom claims
    .set_claim(CustomClaim::new("role", "admin")?)
    .set_claim(CustomClaim::new("permissions", vec!["read", "write"])?)

    .build(&key)?;
```

### Fluent Claim Methods

For convenience, common claims have dedicated methods:

```rust
let token = PasetoBuilder::<V4, Local>::default()
    .subject("user_123")
    .audience("my-app")
    .issuer("auth-service")
    .expiration("2025-01-01T00:00:00Z")?
    .not_before("2024-01-01T00:00:00Z")?
    .issued_at("2024-06-15T12:00:00Z")?
    .token_identifier("token_abc")
    .build(&key)?;
```

### Custom Claims with `.claim()`

The fluent `.claim()` method is the recommended way to add custom claims:

```rust
let token = PasetoBuilder::<V4, Local>::default()
    .subject("user_123")
    .claim("user_id", 42)?
    .claim("roles", vec!["admin", "user"])?
    .claim("metadata", serde_json::json!({"tier": "premium"}))?
    .build(&key)?;
```

This is cleaner than using `set_claim(CustomClaim::new(...))` and supports any serializable value.

---

## Public Tokens (Signing)

For Public purpose tokens, use an asymmetric private key:

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

let private_key = PasetoAsymmetricPrivateKey::<V4, Public>::from(&key_bytes);

let token = PasetoBuilder::<V4, Public>::default()
    .set_claim(SubjectClaim::from("user_123"))
    .set_claim(AudienceClaim::from("api-consumers"))
    .build(&private_key)?;
```

<Note>
  Public tokens are signed, not encrypted. The payload is visible to anyone who
  has the token. Don't include sensitive data.
</Note>

---

## Adding Footers

Footers are unencrypted metadata attached to tokens. They're useful for key identifiers:

```rust
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)?;
```

The footer can be read without decrypting the token, making it useful for key rotation scenarios.

---

## Implicit Assertions

Implicit assertions are data bound to the token cryptographically but not included in the token string:

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

let token = PasetoBuilder::<V4, Local>::default()
    .set_claim(SubjectClaim::from("user_123"))
    .set_implicit_assertion(ImplicitAssertion::from("tenant-id:12345"))
    .build(&key)?;
```

<Note>
  When parsing, you must provide the same implicit assertion or decryption will
  fail. This is useful for binding tokens to specific contexts.
</Note>

---

## Non-Expiring Tokens

By default, tokens include an expiration claim. To create a non-expiring token:

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

let token = PasetoBuilder::<V4, Local>::default()
    .set_claim(SubjectClaim::from("service-account"))
    .set_no_expiration_danger_acknowledged()
    .build(&key)?;
```

<Note>
  Non-expiring tokens are a security risk. Use them only for service-to-service
  authentication where token rotation is handled differently.
</Note>

---

## Complete Example

<Row>
  <Col>

    Here's a complete example creating a token for user authentication:

  </Col>
  <Col>

    ```rust
    use rusty_paseto::prelude::*;
    use time::{Duration, OffsetDateTime};

    fn create_auth_token(
        user_id: &str,
        role: &str,
        key: &PasetoSymmetricKey<V4, Local>,
    ) -> Result<String, Box<dyn std::error::Error>> {
        let now = OffsetDateTime::now_utc();
        let exp = now + Duration::hours(24);

        let token = PasetoBuilder::<V4, Local>::default()
            .set_claim(SubjectClaim::from(user_id))
            .set_claim(AudienceClaim::from("my-app"))
            .set_claim(IssuerClaim::from("auth-service"))
            .set_claim(IssuedAtClaim::try_from(
                now.format(&time::format_description::well_known::Rfc3339)?
            )?)
            .set_claim(ExpirationClaim::try_from(
                exp.format(&time::format_description::well_known::Rfc3339)?
            )?)
            .set_claim(CustomClaim::new("role", role)?)
            .set_footer(Footer::from(r#"{"kid":"v1"}"#))
            .build(key)?;

        Ok(token)
    }
    ```

  </Col>
</Row>