Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/jfbilodeau/jwks-client

JWKS client for Rust
https://github.com/jfbilodeau/jwks-client

Last synced: about 3 hours ago
JSON representation

JWKS client for Rust

Awesome Lists containing this project

README

        

[![Build Status](https://travis-ci.com/jfbilodeau/jwks-client.svg?branch=master)](https://travis-ci.com/jfbilodeau/jwks-client) [![License:MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![License:Apache](https://img.shields.io/badge/License-Apache-yellow.svg)](https://opensource.org/licenses/Apache-2.0) ![Minimum rustc version](https://img.shields.io/badge/rustc-stable-success.svg)

JWKS-Client is a library written in Rust to decode and validate JWT tokens using a JSON Web Key Store.

### Breaking Change
Now at version 2.0. Support for async/await thanks to [Genna Wingert](https://github.com/wingertge).
Requires Rust Stable 1.39 or higher

## ** IMPORTANT **
JWKS-Client was designed to work with a project that uses [Rocket](https://crates.io/crates/rocket). Unfortunately, the version of Rocket in [crates.io](https://crates.io) is not compatible with the version of [Ring](https://crates.io/crates/ring) required for JWKS-Client. Until the next version of Rocket is published, consider using the following in your `Cargo.toml`:

```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...

```

Features
---

### Library wide:
* No panic!
* Build with Rust stable (1.40)
* Designed for a production system (not an academic project)
* Concise results (see [error::Type](https://docs.rs/shared_jwt/latest/shared_jwt/error/enum.Type.html) for example)

### JWKS key store
* Download key set from HTTP address
* Decode JWT tokens into header, payload and signature
* Verify token signature, expiry and not-before
* Determine when keys should be refreshed

### JWT:
* 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.

Basic Usage
---

The following demonstrates how to load a set of keys from an HTTP address and verify a JWT token using those keys:

```rust
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.

```rust
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:

```rust
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:

```rust
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::().unwrap();

println!("Issuer: {}", claims.iss);
println!("Name: {}", claims.name);
println!("Email: {}", claims.email);
}
```

History
---
* 0.2.0
* **Breaking Change**: Support for async/await (Thanks to [Genna Wingert](htps://github.com/wingertge))
* 0.1.8
* Fixed issued https://github.com/jfbilodeau/jwks-client/issues/1 (Thanks to [Tim Schuster](https://github.com/tscs37) for reporting and assisting)
* 0.1.7
* Updated dependencies
* 0.1.6
* Added `key_set::KeyStore::should_refresh()` to test if keys should be refreshed
* Added `key_set::KeyStore::refresh_interval` to determine how early keys should be refreshed before they expire
* Some more documentation
* 0.1.5:
* Added `readme = "README.md"` to `Cargo.toml`
* 0.1.4:
* Updated documentation--specifically how to use JWKS-Client with Rocket
* Added the ability to determine if keys should be refreshed from the `KeyStore`
* Fixed example on this page--they are now directly from `./examples/*`
* 0.1.3:
* Change the license to be MIT/Apache
* Moved demos into `./example`
* Added the ability to verify if keys need to be refreshed in the keystore based on the cache-control header

* 0.1.2: (Sorry for the breaking changes)
* Rename module `jwks` to `keyset`
* Renamed struct `Jwks` to `KeyStore`
* Expanded documentation a bit
* Fixed some demos
* 0.1.1: Original version

TODO:
---
* Lots More documentation :P
* Automatically refresh keys

(Made with ❤️ with Rust)