Crates.io | jwks-client-update |
lib.rs | jwks-client-update |
version | 0.2.1 |
source | src |
created_at | 2023-09-27 11:23:29.919534 |
updated_at | 2023-09-27 18:49:23.790905 |
description | Library to validate JWT tokens using JSON Web Key Set (JWKS) |
homepage | https://github.com/joao-p-pereira/jwks-client-update.git |
repository | https://github.com/joao-p-pereira/jwks-client-update.git |
max_upload_size | |
id | 984505 |
size | 110,667 |
JWKS-Client is a library written in Rust to decode and validate JWT tokens using a JSON Web Key Store.
Now at version 2.0. Support for async/await thanks to Genna Wingert. Requires Rust Stable 1.39 or higher
JWKS-Client was designed to work with a project that uses Rocket. Unfortunately, the version of Rocket in crates.io is not compatible with the version of Ring required for JWKS-Client. Until the next version of Rocket is published, consider using the following in your Cargo.toml
:
[dependencies]
jwks-client = "0.1.4"
rocket = { git = "https://github.com/jfbilodeau/Rocket", version = "0.5.0-dev"}
# Other dependencies...
[dependencies.rocket_contrib]
version = "0.5.0-dev"
git = "https://github.com/jfbilodeau/Rocket"
# Other options...
Transfer header and payload in user-defined struct. See the example below[^1]
Accessor for standard header and payload fields
JWKS-Client was create specifically to decode GCP/Firebase JWT but should be useable with little to no modification. Contact me to propose support for different JWKS key store. Feedback, suggestions, complaints and criticism is appreciated.
The following demonstrates how to load a set of keys from an HTTP address and verify a JWT token using those keys:
use jwks_client::error::Error;
use jwks_client::keyset::KeyStore;
#[tokio::main]
async fn main() {
let jkws_url = "https://raw.githubusercontent.com/jfbilodeau/jwks-client/0.1.8/test/test-jwks.json";
let key_set = KeyStore::new_from(jkws_url.to_owned()).await.unwrap();
// ...
let token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ.eyJuYW1lIjoiQWRhIExvdmVsYWNlIiwiaXNzIjoiaHR0cHM6Ly9jaHJvbm9nZWFycy5jb20vdGVzdCIsImF1ZCI6InRlc3QiLCJhdXRoX3RpbWUiOjEwMCwidXNlcl9pZCI6InVpZDEyMyIsInN1YiI6InNidTEyMyIsImlhdCI6MjAwLCJleHAiOjUwMCwibmJmIjozMDAsImVtYWlsIjoiYWxvdmVsYWNlQGNocm9ub2dlYXJzLmNvbSJ9.eTQnwXrri_uY55fS4IygseBzzbosDM1hP153EZXzNlLH5s29kdlGt2mL_KIjYmQa8hmptt9RwKJHBtw6l4KFHvIcuif86Ix-iI2fCpqNnKyGZfgERV51NXk1THkgWj0GQB6X5cvOoFIdHa9XvgPl_rVmzXSUYDgkhd2t01FOjQeeT6OL2d9KdlQHJqAsvvKVc3wnaYYoSqv2z0IluvK93Tk1dUBU2yWXH34nX3GAVGvIoFoNRiiFfZwFlnz78G0b2fQV7B5g5F8XlNRdD1xmVZXU8X2-xh9LqRpnEakdhecciFHg0u6AyC4c00rlo_HBb69wlXajQ3R4y26Kpxn7HA";
match key_set.verify(token) {
Ok(jwt) => {
println!("name={}", jwt.payload().get_str("name").unwrap());
}
Err(Error { msg, typ: _ }) => {
eprintln!("Could not verify token. Reason: {}", msg);
}
}
}
JWKS-Client can be use to simply decode a JWT token without validating the signature.
use jwks_client::keyset::KeyStore;
fn main() {
let key_store = KeyStore::new();
let token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ.eyJuYW1lIjoiQWRhIExvdmVsYWNlIiwiaXNzIjoiaHR0cHM6Ly9jaHJvbm9nZWFycy5jb20vdGVzdCIsImF1ZCI6InRlc3QiLCJhdXRoX3RpbWUiOjEwMCwidXNlcl9pZCI6InVpZDEyMyIsInN1YiI6InNidTEyMyIsImlhdCI6MjAwLCJleHAiOjUwMCwibmJmIjozMDAsImVtYWlsIjoiYWxvdmVsYWNlQGNocm9ub2dlYXJzLmNvbSJ9.eTQnwXrri_uY55fS4IygseBzzbosDM1hP153EZXzNlLH5s29kdlGt2mL_KIjYmQa8hmptt9RwKJHBtw6l4KFHvIcuif86Ix-iI2fCpqNnKyGZfgERV51NXk1THkgWj0GQB6X5cvOoFIdHa9XvgPl_rVmzXSUYDgkhd2t01FOjQeeT6OL2d9KdlQHJqAsvvKVc3wnaYYoSqv2z0IluvK93Tk1dUBU2yWXH34nX3GAVGvIoFoNRiiFfZwFlnz78G0b2fQV7B5g5F8XlNRdD1xmVZXU8X2-xh9LqRpnEakdhecciFHg0u6AyC4c00rlo_HBb69wlXajQ3R4y26Kpxn7HA";
let jwt = key_store.decode(token).unwrap();
if jwt.expired().unwrap_or(false) {
println!("Sorry, token expired")
} else {
let result = jwt.payload().get_str("name");
match result {
Some(name) => {
println!("Welcome, {}!", name);
}
None => {
println!("Welcome, anonymous");
}
}
}
}
JWKS-Client offers descriptive error results:
use jwks_client::error::{Error, Type};
use jwks_client::keyset::KeyStore;
#[rustfmt::skip]
#[tokio::main]
async fn main() {
let url = "https://raw.githubusercontent.com/jfbilodeau/jwks-client/0.1.8/test/test-jwks.json";
let token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ.eyJuYW1lIjoiQWRhIExvdmVsYWNlIiwiaXNzIjoiaHR0cHM6Ly9jaHJvbm9nZWFycy5jb20vdGVzdCIsImF1ZCI6InRlc3QiLCJhdXRoX3RpbWUiOjEwMCwidXNlcl9pZCI6InVpZDEyMyIsInN1YiI6InNidTEyMyIsImlhdCI6MjAwLCJleHAiOjUwMCwibmJmIjozMDAsImVtYWlsIjoiYWxvdmVsYWNlQGNocm9ub2dlYXJzLmNvbSJ9.eTQnwXrri_uY55fS4IygseBzzbosDM1hP153EZXzNlLH5s29kdlGt2mL_KIjYmQa8hmptt9RwKJHBtw6l4KFHvIcuif86Ix-iI2fCpqNnKyGZfgERV51NXk1THkgWj0GQB6X5cvOoFIdHa9XvgPl_rVmzXSUYDgkhd2t01FOjQeeT6OL2d9KdlQHJqAsvvKVc3wnaYYoSqv2z0IluvK93Tk1dUBU2yWXH34nX3GAVGvIoFoNRiiFfZwFlnz78G0b2fQV7B5g5F8XlNRdD1xmVZXU8X2-xh9LqRpnEakdhecciFHg0u6AyC4c00rlo_HBb69wlXajQ3R4y26Kpxn7HA";
let key_set = KeyStore::new_from(url.to_owned()).await.unwrap();
match key_set.verify(token) {
Ok(jwt) => {
println!("name={}", jwt.payload().get_str("name").unwrap());
}
Err(Error {
msg,
typ: Type::Header,
}) => {
eprintln!("Problem with header. Message: {}", msg);
}
Err(Error {
msg,
typ: Type::Payload,
}) => {
eprintln!("Problem with payload. Message: {}", msg);
}
Err(Error {
msg,
typ: Type::Signature,
}) => {
eprintln!("Problem with signature. Message: {}", msg);
}
Err(Error {
msg: _,
typ: Type::Expired,
}) => {
eprintln!("Token is expired.");
}
Err(Error {
msg: _,
typ: Type::Early,
}) => {
eprintln!("Too early to use token.");
}
Err(e) => {
eprintln!("Something else went wrong. Message {:?}", e);
}
}
}
[^1] JWKS-Client can decode a JWT payload (claims) into a struct:
use serde_derive::Deserialize;
use jwks_client::keyset::KeyStore;
fn main() {
#[derive(Deserialize)]
pub struct MyClaims {
pub iss: String,
pub name: String,
pub email: String,
}
let url = "https://raw.githubusercontent.com/jfbilodeau/jwks-client/0.1.8/test/test-jwks.json";
let key_store = KeyStore::new_from(url.to_owned()).unwrap();
let token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ.eyJuYW1lIjoiQWRhIExvdmVsYWNlIiwiaXNzIjoiaHR0cHM6Ly9jaHJvbm9nZWFycy5jb20vdGVzdCIsImF1ZCI6InRlc3QiLCJhdXRoX3RpbWUiOjEwMCwidXNlcl9pZCI6InVpZDEyMyIsInN1YiI6InNidTEyMyIsImlhdCI6MjAwLCJleHAiOjUwMCwibmJmIjozMDAsImVtYWlsIjoiYWxvdmVsYWNlQGNocm9ub2dlYXJzLmNvbSJ9.eTQnwXrri_uY55fS4IygseBzzbosDM1hP153EZXzNlLH5s29kdlGt2mL_KIjYmQa8hmptt9RwKJHBtw6l4KFHvIcuif86Ix-iI2fCpqNnKyGZfgERV51NXk1THkgWj0GQB6X5cvOoFIdHa9XvgPl_rVmzXSUYDgkhd2t01FOjQeeT6OL2d9KdlQHJqAsvvKVc3wnaYYoSqv2z0IluvK93Tk1dUBU2yWXH34nX3GAVGvIoFoNRiiFfZwFlnz78G0b2fQV7B5g5F8XlNRdD1xmVZXU8X2-xh9LqRpnEakdhecciFHg0u6AyC4c00rlo_HBb69wlXajQ3R4y26Kpxn7HA";
let jwt = key_store.decode(token).unwrap();
let claims = jwt.payload().into::<MyClaims>().unwrap();
println!("Issuer: {}", claims.iss);
println!("Name: {}", claims.name);
println!("Email: {}", claims.email);
}
0.2.0
0.1.8
0.1.7
0.1.6
key_set::KeyStore::should_refresh()
to test if keys should be refreshedkey_set::KeyStore::refresh_interval
to determine how early keys should be refreshed before they expire0.1.5:
readme = "README.md"
to Cargo.toml
0.1.4:
KeyStore
./examples/*
0.1.3:
./example
0.1.2: (Sorry for the breaking changes)
jwks
to keyset
Jwks
to KeyStore
0.1.1: Original version
(Made with ❤️ with Rust)