jwt_rs 0.1.0

A Json Web Token (JWT) implementation for Rust
Documentation
//! A simple Json Web Token crate. The crate let's you create, sign, verify and extract data from
//! JWT tokens. Data is serialized with `serde` and `serde_json`.
//!
//! # Example
//! ## Verification
//! Extract the payload from a JWT token if the token is valid.
//! ```rust
//! # use jwt_rs::{Token, Algorithm, Error};
//! # use std::str::FromStr;
//! #[derive(serde::Serialize, serde::Deserialize, PartialEq)]
//! struct TestPayload {
//!     is_admin: bool,
//!     name: String,
//!     age: u8,
//! }
//!
//! # fn try_main() -> Result<(), Error> {
//! const SECRET: &'static str = "This is a very secret secret";
//!
//! let token_str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc19hZG1pbiI6dHJ1ZSwibmFtZSI6IkpvaG4gRG9lIiwiYWdlIjoxOH0.0mV5XVAmarscyZEwl8PoX4vqVn_JCZSVJRsgnSJTo94";
//! let token = Token::from_str(token_str)?;
//! // payload is `Ok(Some(<payload>))`
//! let payload = token.get_if_valid::<TestPayload>(SECRET);
//! # Ok(())
//! # }
//! # try_main().unwrap();
//! ```
//!
//! ## Signing
//! It's also possible to create and sign a new token:
//! ```rust
//! # use jwt_rs::{Token, Algorithm};
//! const SECRET: &'static str = "This is a very secret secret";
//!
//! #[derive(serde::Serialize, serde::Deserialize)]
//! struct TestPayload {
//!     is_admin: bool,
//!     name: String,
//!     age: u8,
//! }
//! let payload = TestPayload {
//!     is_admin: true,
//!     name: String::from("John Doe"),
//!     age: 18
//! };
//! let token = Token::try_new(Algorithm::HS256, payload, SECRET).unwrap();
//! println!("{}", token);
//! ```
mod tests;
use base64;
use hmac::{digest, digest::core_api, digest::generic_array::typenum, Mac};
use std::fmt;
use std::str::FromStr;

const SEPARATOR: &'static str = ".";

/// Errors generated by this crate.
#[derive(Debug, PartialEq)]
pub enum Error {
    /// There was no header section found in the token.
    HeaderNotFound,
    /// There was no payload section found in the token.
    PayloadNotFound,
    /// There was no signature section found in the token.
    SignatureNotFound,
    /// There was more than 3 sections found in the token.
    TooManySections,
    /// The encoded bytes is not valid base64
    InvalidBase64(base64::DecodeError),
    /// The byte string is not valid utf-8, but it should be.
    InvalidUtf8(std::string::FromUtf8Error),
    /// The header is not in a valid format.
    InvalidHeader,
    /// The length of the secret is invalid.
    HashSecret(digest::InvalidLength),
    /// An error occured while deserializing or serializing the payload.
    SerializePayload,
}

impl From<base64::DecodeError> for Error {
    fn from(err: base64::DecodeError) -> Error {
        Error::InvalidBase64(err)
    }
}

impl From<std::string::FromUtf8Error> for Error {
    fn from(err: std::string::FromUtf8Error) -> Error {
        Error::InvalidUtf8(err)
    }
}

impl From<digest::InvalidLength> for Error {
    fn from(err: digest::InvalidLength) -> Error {
        Error::HashSecret(err)
    }
}

impl From<serde_json::Error> for Error {
    fn from(_: serde_json::Error) -> Error {
        Error::SerializePayload
    }
}

/// The hash algorithm that is used to sign the token. Currently, only HMAC (signing with a screet)
/// is supported by this crate.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum Algorithm {
    HS256,
    HS512,
    HS384,
}

impl Algorithm {
    fn to_bytes(&self, payload: &str, secret: &str) -> Result<Vec<u8>, Error> {
        match self {
            Self::HS256 => Self::to_bytes_hmac::<sha2::Sha256>(payload, secret),
            Self::HS384 => Self::to_bytes_hmac::<sha2::Sha384>(payload, secret),
            Self::HS512 => Self::to_bytes_hmac::<sha2::Sha512>(payload, secret),
        }
    }

    fn to_bytes_hmac<H>(payload: &str, secret: &str) -> Result<Vec<u8>, Error>
    where
        H: core_api::CoreProxy,
        H::Core: digest::HashMarker
            + core_api::UpdateCore
            + core_api::FixedOutputCore
            + core_api::BufferKindUser<BufferKind = digest::block_buffer::Eager>
            + Default
            + Clone,
        <H::Core as core_api::BlockSizeUser>::BlockSize: typenum::IsLess<typenum::U256>,
        typenum::Le<<H::Core as core_api::BlockSizeUser>::BlockSize, typenum::U256>:
            typenum::NonZero,
    {
        let mut mac = hmac::Hmac::<H>::new_from_slice(secret.as_bytes())?;
        mac.update(payload.as_bytes());
        let bytes = mac.finalize();
        Ok(bytes.into_bytes()[..].to_vec())
    }
}

/// A JWT token. Use this struct to sign or verify a token. You can get the payload or verify a
/// token with the `Token::from_str` function.
#[derive(Debug)]
pub struct Token {
    header: Header,
    payload: Payload,
    signature: Signature,
}

impl Token {
    /// Verify the token with a given secret.
    ///
    /// # Example
    ///
    /// ```rust
    /// # use jwt_rs::Token;
    /// # use std::str::FromStr;
    /// const SECRET: &'static str = "This is a very secret secret";
    ///
    /// let token_str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc19hZG1pbiI6dHJ1ZSwibmFtZSI6IkpvaG4gRG9lIiwiYWdlIjoxOH0.0mV5XVAmarscyZEwl8PoX4vqVn_JCZSVJRsgnSJTo94";
    /// let token = Token::from_str(token_str).unwrap();
    /// assert_eq!(Ok(true), token.is_valid(SECRET));
    /// ```
    pub fn is_valid(&self, secret: &str) -> Result<bool, Error> {
        let signature = Signature::try_new(&self.header, &self.payload, secret)?;
        Ok(signature.0 == self.signature.0)
    }

    /// Try creating a new signed token.
    ///
    /// # Example
    ///
    /// ```rust
    /// # use jwt_rs::{Token, Algorithm};
    /// const SECRET: &'static str = "This is a very secret secret";
    ///
    /// #[derive(serde::Serialize, serde::Deserialize, PartialEq)]
    /// struct TestPayload {
    ///     is_admin: bool,
    ///     name: String,
    ///     age: u8,
    /// }
    /// let payload = TestPayload {
    ///     is_admin: true,
    ///     name: String::from("John Doe"),
    ///     age: 18
    /// };
    /// let token = Token::try_new(Algorithm::HS256, payload, SECRET);
    /// ```
    pub fn try_new<T: serde::Serialize>(
        alg: Algorithm,
        payload: T,
        secret: &str,
    ) -> Result<Self, Error> {
        let header = Header::new(alg);
        let payload = Payload::try_new(payload)?;
        let signature = Signature::try_new(&header, &payload, secret)?;
        Ok(Self {
            signature,
            header,
            payload,
        })
    }

    /// Return the payload of the token if the token is valid. If the token is not valid `Ok(None)`
    /// is returned. An error may occured while deserializing the paylaod if the payload is not
    /// valid json.
    ///
    /// # Example
    ///
    /// ```rust
    /// # use jwt_rs::Token;
    /// # use std::str::FromStr;
    /// const SECRET: &'static str = "This is a very secret secret";
    ///
    /// #[derive(serde::Serialize, serde::Deserialize, PartialEq, Debug)]
    /// struct TestPayload {
    ///     is_admin: bool,
    ///     name: String,
    ///     age: u8,
    /// }
    /// let reference_payload = TestPayload {
    ///     is_admin: true,
    ///     name: String::from("John Doe"),
    ///     age: 18
    /// };
    /// let token_str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc19hZG1pbiI6dHJ1ZSwibmFtZSI6IkpvaG4gRG9lIiwiYWdlIjoxOH0.0mV5XVAmarscyZEwl8PoX4vqVn_JCZSVJRsgnSJTo94";
    /// let token = Token::from_str(token_str).unwrap();
    /// let payload = token.get_if_valid::<TestPayload>(SECRET);
    /// assert_eq!(Ok(Some(reference_payload)), payload);
    /// ```
    pub fn get_if_valid<'a, T: serde::Deserialize<'a>>(
        &'a self,
        secret: &str,
    ) -> Result<Option<T>, Error> {
        if self.is_valid(secret)? {
            Ok(Some(serde_json::from_str::<T>(&self.payload.decoded)?))
        } else {
            Ok(None)
        }
    }
}

impl fmt::Display for Token {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{}{}{}{}{}",
            self.header.encoded, SEPARATOR, self.payload.encoded, SEPARATOR, self.signature
        )
    }
}

impl FromStr for Token {
    type Err = Error;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut parts = s.split(SEPARATOR);
        let token = Self {
            header: parts.next().ok_or(Error::HeaderNotFound)?.parse()?,
            payload: parts.next().ok_or(Error::PayloadNotFound)?.parse()?,
            signature: parts.next().ok_or(Error::SignatureNotFound)?.parse()?,
        };
        if parts.next().is_some() {
            Err(Error::TooManySections)
        } else {
            Ok(token)
        }
    }
}

/// The header part of the JWT token. This part contains the algorithm used to hash the signature.
#[derive(Debug)]
pub struct Header {
    /// The base64 encoded data is kept here because it's used in the verifiying process.
    encoded: String,
    /// The actual data that is encoded. A second struct is used because it's easyer to decode with
    /// serde_json.
    decoded: HeaderDecoded,
}

impl Header {
    pub fn new(alg: Algorithm) -> Self {
        let decoded = HeaderDecoded::new(alg);
        let serialized = serde_json::to_string(&decoded).expect("Should always serialized");
        Self {
            encoded: base64::encode_config(serialized, base64::URL_SAFE_NO_PAD),
            decoded,
        }
    }
}

/// The actual data that is encoded in the header is stored in this struct. A second struct is used
/// because it's easyer to decode with serde_json.
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[allow(dead_code)]
pub struct HeaderDecoded {
    alg: Algorithm,
    typ: String,
}

impl HeaderDecoded {
    pub fn new(alg: Algorithm) -> Self {
        Self {
            alg,
            typ: String::from("JWT"),
        }
    }
}

impl FromStr for Header {
    type Err = Error;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let bytes = base64::decode(s)?;
        let header = String::from_utf8(bytes)?;
        match serde_json::from_str::<HeaderDecoded>(&header) {
            Ok(decoded) => Ok(Self {
                encoded: s.to_string(),
                decoded,
            }),
            Err(_) => Err(Error::InvalidHeader),
        }
    }
}

/// The payload of the JWT token. The payload can contain any JSON formatted data. This is the part
/// where data is stored.
#[derive(Debug)]
pub struct Payload {
    decoded: String,
    encoded: String,
}

impl Payload {
    pub fn try_new<T: serde::Serialize>(payload: T) -> Result<Self, Error> {
        let decoded = serde_json::to_string(&payload)?;
        let encoded = base64::encode_config(&decoded, base64::URL_SAFE_NO_PAD);
        Ok(Payload { decoded, encoded })
    }
}

impl FromStr for Payload {
    type Err = Error;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        // First we check if the base64 is valid
        let bytes = base64::decode(s)?;
        let decoded = String::from_utf8(bytes)?;
        Ok(Payload {
            decoded,
            encoded: s.to_string(),
        })
    }
}

/// The signature of the token. This part is used to check that the token is valid and was not
/// tempered with.
#[derive(Debug)]
pub struct Signature(Vec<u8>);

impl Signature {
    pub fn try_new(header: &Header, payload: &Payload, secret: &str) -> Result<Self, Error> {
        let full_payload = format!("{}{}{}", header.encoded, SEPARATOR, payload.encoded);
        let bytes = header.decoded.alg.to_bytes(&full_payload, &secret)?;
        Ok(Self(bytes))
    }
}

impl fmt::Display for Signature {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{}",
            base64::encode_config(&self.0, base64::URL_SAFE_NO_PAD)
        )
    }
}

impl FromStr for Signature {
    type Err = Error;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let bytes = base64::decode_config(s, base64::URL_SAFE_NO_PAD)?;
        Ok(Signature(bytes))
    }
}