From 6bac1bdbf0bb33b2b95d5c0430c99e1cb56ba417 Mon Sep 17 00:00:00 2001 From: Kellen Frodelius-Fujimoto Date: Mon, 1 Apr 2019 12:11:28 +0200 Subject: [PATCH] Add example of using `chrono::DateTime` in claims Using `chrono`'s `serde` feature uses ISO 8601 instead of a Unix timestamp as specified in RFC 7519 section 2, "NumericDate". This example uses custom de/serialize functions as shown in the [serde.rs example, "Custom Date Format"](https://serde.rs/custom-date-format.html). NOTE: Currently fractional values are not supported in the example, though they are in the spec. --- examples/custom_chrono.rs | 101 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 examples/custom_chrono.rs diff --git a/examples/custom_chrono.rs b/examples/custom_chrono.rs new file mode 100644 index 0000000..7fd7b3a --- /dev/null +++ b/examples/custom_chrono.rs @@ -0,0 +1,101 @@ +extern crate jsonwebtoken as jwt; +extern crate serde; +#[macro_use] +extern crate serde_derive; +extern crate chrono; + +use chrono::prelude::*; +use jwt::{Header, Validation}; + +const SECRET: &str = "some-secret"; + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +struct Claims { + sub: String, + #[serde(with = "jwt_numeric_date")] + iat: DateTime, + #[serde(with = "jwt_numeric_date")] + exp: DateTime, +} + +mod jwt_numeric_date { + //! Custom serialization of DateTime to conform with the JWT spec (RFC 7519 section 2, "Numeric Date") + use chrono::{DateTime, TimeZone, Utc}; + use serde::{self, Deserialize, Deserializer, Serializer}; + + /// Serializes a DateTime to a Unix timestamp (milliseconds since 1970/1/1T00:00:00T) + pub fn serialize(date: &DateTime, serializer: S) -> Result + where + S: Serializer, + { + let timestamp = date.timestamp(); + serializer.serialize_i64(timestamp) + } + + /// Attempts to deserialize an i64 and use as a Unix timestamp + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + Utc.timestamp_opt(i64::deserialize(deserializer)?, 0) + .single() // If there are multiple or no valid DateTimes from timestamp, return None + .ok_or_else(|| serde::de::Error::custom("invalid Unix timestamp value")) + } + + #[cfg(test)] + mod tests { + use super::*; + use jwt::{Header, Validation}; + + const EXPECTED_TOKEN: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJDdXN0b20gRGF0ZVRpbWUgc2VyL2RlIiwiaWF0IjowLCJleHAiOjMyNTAzNjgwMDAwfQ.RTgha0S53MjPC2pMA4e2oMzaBxSY3DMjiYR2qFfV55A"; + + use super::super::{Claims, SECRET}; + + #[test] + fn round_trip() { + let sub = "Custom DateTime ser/de".to_string(); + let iat = Utc.timestamp(0, 0); + let exp = Utc.timestamp(32503680000, 0); + + let claims = Claims { sub: sub.clone(), iat, exp }; + + let token = jwt::encode(&Header::default(), &claims, SECRET.as_ref()) + .expect("Failed to encode claims"); + + assert_eq!(&token, EXPECTED_TOKEN); + + let decoded = jwt::decode::(&token, SECRET.as_ref(), &Validation::default()) + .expect("Failed to decode token"); + + assert_eq!(decoded.claims, claims); + } + + #[test] + fn should_fail_on_invalid_timestamp() { + // A token with the expiry of i64::MAX + 1 + let overflow_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJDdXN0b20gRGF0ZVRpbWUgc2VyL2RlIiwiaWF0IjowLCJleHAiOjkyMjMzNzIwMzY4NTQ3NzYwMDB9.G2PKreA27U8_xOwuIeCYXacFYeR46f9FyENIZfCrvEc"; + + let decode_result = + jwt::decode::(&overflow_token, SECRET.as_ref(), &Validation::default()); + + assert!(decode_result.is_err()); + } + } +} + +fn main() -> Result<(), Box> { + let sub = "Custom DateTime ser/de".to_string(); + let iat = Utc::now(); + let exp = iat + chrono::Duration::days(1); + + let claims = Claims { sub: sub.clone(), iat, exp }; + + let token = jwt::encode(&Header::default(), &claims, SECRET.as_ref())?; + + println!("serialized token: {}", &token); + + let token_data = jwt::decode::(&token, SECRET.as_ref(), &Validation::default())?; + + println!("token data:\n{:#?}", &token_data); + Ok(()) +}