5 releases (3 breaking)

0.4.1 Feb 12, 2021
0.4.0 Feb 11, 2021
0.3.1 Feb 11, 2021
0.2.0 Feb 10, 2021
0.1.0 Feb 10, 2021

#9 in #scope

48 downloads per month
Used in tinychain

Apache-2.0

22KB
438 lines

rjwt

A recursive Javascript Web Token library for Rust


lib.rs:

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 futures::executor::block_on;
# use async_trait::async_trait;
# use rjwt::*;

# #[derive(Clone)]
struct Resolver {
    host: String,
    actors: HashMap<String, Actor<String>>,
    peers: HashMap<String, Resolver>,
}
// ...
# impl Resolver {
#    fn new(host: String, actor: Actor<String>, peers: Vec<Resolver>) -> Self {
#         let peers = peers.into_iter().map(|peer| (peer.host(), peer)).collect();
#         let actors = vec![(actor.id().to_string(), actor)].into_iter().collect();
#         Self { host, actors, peers }
#    }
# }

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

    fn host(&self) -> String {
        self.host.clone()
    }

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

let now = SystemTime::now();

// Say that Bob is a user at 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 retail_app = Actor::new("app".to_string());
let retailer = Resolver::new(
    "retailer.com".to_string(), 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, vec![retailer.clone(), example]);

// 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");
let bobs_token = Token::new(
    example_dot_com.clone(),
    now,
    Duration::from_secs(30),
    actor_bob.id().to_string(),
    bobs_claim);

let bobs_token = actor_bob.sign_token(&bobs_token).unwrap();

// Then, retailer.com consumes the token (validating it in the process).
let retailer_claim = String::from("Bob spent $1 on retailer.com");
let (retailer_token, _) = block_on(
    retailer.consume_and_sign(&retail_app, retailer_claim, bobs_token, now)).unwrap();

// Finally, Bob's bank validates the token to verify that the request came from Bob.
let claims = block_on(bank.validate(&retailer_token, now)).unwrap();
assert!(claims.get(&example_dot_com, &bobs_id).unwrap().starts_with("I am Bob"));

Dependencies

~4.5MB
~98K SLoC