use chrono::prelude::*; use jsonwebtoken::{Header, Validation}; use serde::{Deserialize, Serialize}; 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 { 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 = encode(&Header::default(), &claims, SECRET.as_ref()) .expect("Failed to encode claims"); assert_eq!(&token, EXPECTED_TOKEN); let decoded = 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 = 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 = jsonwebtoken::encode(&Header::default(), &claims, SECRET.as_ref())?; println!("serialized token: {}", &token); let token_data = jsonwebtoken::decode::(&token, SECRET.as_ref(), &Validation::default())?; println!("token data:\n{:#?}", &token_data); Ok(()) }