rjwt 0.5.0

A recursive Javascript Web Token with support for ECDSA and Falcon
Documentation

Provides an [Actor] and (de)serializable [Token] struct which support authenticating JSON Web Tokens with a custom payload. See jwt.io for more information on the JWT spec.

The provided [Actor] uses the ECDSA algorithm to sign tokens (using the [ed25519_dalek] crate).

This library differs from other JWT implementations in that it allows for recursive [Token]s.

Note that if the same (host, actor) pair is specified multiple times in the token chain, only the latest is returned by [Claims::get].

Example:

# use std::collections::HashMap;
# use std::time::{Duration, SystemTime};
# use async_trait::async_trait;
# use futures::executor::block_on;
use rjwt::*;

#[derive(Clone)]
struct Resolver {
hostname: String,
actors: HashMap<String, Actor<String>>,
peers: Vec<Self>,
}
// ...
# impl Resolver {
#    fn new<A: IntoIterator<Item = Actor<String>>>(hostname: String, actors: A, peers: Vec<Self>) -> Self {
#        Self { hostname, actors: actors.into_iter().map(|a| (a.id().clone(), a)).collect(), peers }
#    }
# }

#[async_trait]
impl Resolve for Resolver {
type HostId = String;
type ActorId = String;
type Claims = String;

async fn resolve(&self, host: &Self::HostId, actor_id: &Self::ActorId) -> Result<Actor<Self::ActorId>, Error> {
if host == &self.hostname {
self.actors.get(actor_id).cloned().ok_or_else(|| Error::fetch(actor_id))
} else if let Some(peer) = self.peers.iter().filter(|p| &p.hostname == host).next() {
peer.resolve(host, actor_id).await
} else {
Err(Error::fetch(host))
}
}
}

let now = SystemTime::now();

// Say that Bob is a user on example.com.
let bobs_id = "bob".to_string();
let example_dot_com = "example.com".to_string();

let actor_bob = Actor::new(bobs_id.clone());
let example = Resolver::new(example_dot_com.clone(), [actor_bob.clone()], vec![]);

// Bob makes a request through the retailer.com app.
let retailer_dot_com = "retailer.com".to_string();
let retail_app = Actor::new("app".to_string());
let retailer = Resolver::new(
retailer_dot_com.clone(),
[retail_app.clone()],
vec![example.clone()]);

// The retailer.com app makes a request to Bob's bank.
let bank_account = Actor::new("bank".to_string());
let bank = Resolver::new(
"bank.com".to_string(),
[bank_account.clone()],
vec![example, retailer.clone()]);

// First, example.com issues a token to authenticate Bob.
let bobs_claim = String::from("I am Bob and retailer.com may debit my bank.com account");

// This requires constructing the token...
let bobs_token = Token::new(
example_dot_com.clone(),
now,
Duration::from_secs(30),
actor_bob.id().to_string(),
bobs_claim.clone());

// and signing it with Bob's private key.
let bobs_token = actor_bob.sign_token(bobs_token).expect("signed token");

// Then, retailer.com validates the token...
let bobs_token = block_on(retailer.verify(bobs_token.into_jwt(), now)).expect("claims");
assert!(bobs_token.get_claim(&example_dot_com, &bobs_id).expect("claim").starts_with("I am Bob"));

// and adds its own claim, that Bob owes it $1.
let retailer_claim = String::from("Bob spent $1 on retailer.com");
let retailer_token = retail_app.consume_and_sign(
bobs_token,
retailer_dot_com.clone(),
retailer_claim.clone(),
now).expect("signed token");

assert_eq!(retailer_token.get_claim(&retailer_dot_com, retail_app.id()), Some(&retailer_claim));
assert_eq!(retailer_token.get_claim(&example_dot_com, actor_bob.id()), Some(&bobs_claim));

// Finally, Bob's bank verifies the token...
let retailer_token_as_received = block_on(bank.verify(retailer_token.jwt().to_string(), now)).expect("claims");
assert_eq!(retailer_token, retailer_token_as_received);

// to authenticate that the request came from Bob...
assert!(retailer_token_as_received
.get_claim(&example_dot_com, &bobs_id)
.expect("claim")
.starts_with("I am Bob and retailer.com may debit my bank.com account"));

// via retailer.com.
assert!(retailer_token_as_received.get_claim(&retailer_dot_com, retail_app.id()).expect("claim").starts_with("Bob spent $1"));