jwks-client 0.2.0

Library to validate JWT tokens using JSON Web Key Set (JWKS)
Documentation
[![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).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).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).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);
}
```

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)