Add validation
This commit is contained in:
parent
bdeefe5ed7
commit
410499e6b6
|
@ -11,10 +11,11 @@ keywords = ["jwt", "web", "api", "token", "json"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rustc-serialize = "^0.3"
|
rustc-serialize = "^0.3"
|
||||||
error-chain = "0.9"
|
error-chain = "0.10"
|
||||||
serde_json = "0.9"
|
serde_json = "0.9"
|
||||||
serde_derive = "0.9"
|
serde_derive = "0.9"
|
||||||
serde = "0.9"
|
serde = "0.9"
|
||||||
ring = { version = "0.7", features = ["rsa_signing", "dev_urandom_fallback"] }
|
ring = { version = "0.7", features = ["rsa_signing", "dev_urandom_fallback"] }
|
||||||
base64 = "0.4"
|
base64 = "0.4"
|
||||||
untrusted = "0.3"
|
untrusted = "0.3"
|
||||||
|
chrono = "0.3"
|
||||||
|
|
|
@ -4,7 +4,7 @@ extern crate jsonwebtoken as jwt;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
|
|
||||||
use jwt::{encode, decode, Algorithm, Header};
|
use jwt::{encode, decode, Algorithm, Header, Validation};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||||
struct Claims {
|
struct Claims {
|
||||||
|
@ -25,5 +25,5 @@ fn bench_encode(b: &mut test::Bencher) {
|
||||||
#[bench]
|
#[bench]
|
||||||
fn bench_decode(b: &mut test::Bencher) {
|
fn bench_decode(b: &mut test::Bencher) {
|
||||||
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ";
|
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ";
|
||||||
b.iter(|| decode::<Claims>(token, "secret".as_ref(), Algorithm::HS256));
|
b.iter(|| decode::<Claims>(token, "secret".as_ref(), Algorithm::HS256, Validation::default()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
extern crate jsonwebtoken as jwt;
|
extern crate jsonwebtoken as jwt;
|
||||||
#[macro_use] extern crate serde_derive;
|
#[macro_use] extern crate serde_derive;
|
||||||
|
|
||||||
use jwt::{encode, decode, Header, Algorithm};
|
use jwt::{encode, decode, Header, Algorithm, Validation};
|
||||||
use jwt::errors::{ErrorKind};
|
use jwt::errors::{ErrorKind};
|
||||||
|
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ fn main() {
|
||||||
|
|
||||||
println!("{:?}", token);
|
println!("{:?}", token);
|
||||||
|
|
||||||
let token_data = match decode::<Claims>(&token, key.as_ref(), Algorithm::HS256) {
|
let token_data = match decode::<Claims>(&token, key.as_ref(), Algorithm::HS256, Validation::default()) {
|
||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
Err(err) => match *err.kind() {
|
Err(err) => match *err.kind() {
|
||||||
ErrorKind::InvalidToken => panic!(), // Example on how to handle a specific error
|
ErrorKind::InvalidToken => panic!(), // Example on how to handle a specific error
|
||||||
|
|
|
@ -2,7 +2,7 @@ extern crate jsonwebtoken as jwt;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
|
|
||||||
use jwt::{encode, decode, Header, Algorithm};
|
use jwt::{encode, decode, Header, Algorithm, Validation};
|
||||||
use jwt::errors::{ErrorKind};
|
use jwt::errors::{ErrorKind};
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ fn main() {
|
||||||
Err(_) => panic!() // in practice you would return the error
|
Err(_) => panic!() // in practice you would return the error
|
||||||
};
|
};
|
||||||
|
|
||||||
let token_data = match decode::<Claims>(&token, key.as_ref(), Algorithm::HS512) {
|
let token_data = match decode::<Claims>(&token, key.as_ref(), Algorithm::HS512, Validation::default()) {
|
||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
Err(err) => match *err.kind() {
|
Err(err) => match *err.kind() {
|
||||||
ErrorKind::InvalidToken => panic!(), // Example on how to handle a specific error
|
ErrorKind::InvalidToken => panic!(), // Example on how to handle a specific error
|
||||||
|
|
|
@ -10,7 +10,8 @@ use untrusted;
|
||||||
|
|
||||||
use errors::{Result, ErrorKind};
|
use errors::{Result, ErrorKind};
|
||||||
use header::Header;
|
use header::Header;
|
||||||
use serialization::{from_jwt_part, to_jwt_part, TokenData};
|
use serialization::{from_jwt_part, to_jwt_part, from_jwt_part_claims, TokenData};
|
||||||
|
use validation::{Validation, validate};
|
||||||
|
|
||||||
|
|
||||||
/// The algorithms supported for signing/verifying
|
/// The algorithms supported for signing/verifying
|
||||||
|
@ -112,7 +113,6 @@ pub fn verify(signature: &str, signing_input: &str, key: &[u8], algorithm: Algor
|
||||||
message,
|
message,
|
||||||
expected_signature,
|
expected_signature,
|
||||||
);
|
);
|
||||||
println!("{:?}", res);
|
|
||||||
|
|
||||||
Ok(res.is_ok())
|
Ok(res.is_ok())
|
||||||
},
|
},
|
||||||
|
@ -131,14 +131,14 @@ macro_rules! expect_two {
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decode fn used internally by `decode` and `decode_without_verifying`
|
/// Decode a token into a struct containing Claims and Header
|
||||||
fn internal_decode<T: Deserialize>(token: &str, key: &[u8], algorithm: Algorithm, do_verification: bool) -> Result<TokenData<T>> {
|
///
|
||||||
|
/// If the token or its signature is invalid, it will return an error
|
||||||
|
pub fn decode<T: Deserialize>(token: &str, key: &[u8], algorithm: Algorithm, validation: Validation) -> Result<TokenData<T>> {
|
||||||
let (signature, signing_input) = expect_two!(token.rsplitn(2, '.'));
|
let (signature, signing_input) = expect_two!(token.rsplitn(2, '.'));
|
||||||
|
|
||||||
if do_verification {
|
if validation.validate_signature && !verify(signature, signing_input, key, algorithm)? {
|
||||||
if !verify(signature, signing_input, key, algorithm)? {
|
return Err(ErrorKind::InvalidSignature.into());
|
||||||
return Err(ErrorKind::InvalidSignature.into());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let (claims, header) = expect_two!(signing_input.rsplitn(2, '.'));
|
let (claims, header) = expect_two!(signing_input.rsplitn(2, '.'));
|
||||||
|
@ -147,22 +147,9 @@ fn internal_decode<T: Deserialize>(token: &str, key: &[u8], algorithm: Algorithm
|
||||||
if header.alg != algorithm {
|
if header.alg != algorithm {
|
||||||
return Err(ErrorKind::WrongAlgorithmHeader.into());
|
return Err(ErrorKind::WrongAlgorithmHeader.into());
|
||||||
}
|
}
|
||||||
let decoded_claims: T = from_jwt_part(claims)?;
|
let (decoded_claims, claims_map): (T, _) = from_jwt_part_claims(claims)?;
|
||||||
|
|
||||||
|
validate(&claims_map, &validation)?;
|
||||||
|
|
||||||
Ok(TokenData { header: header, claims: decoded_claims })
|
Ok(TokenData { header: header, claims: decoded_claims })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decode a token into a struct containing Claims and Header
|
|
||||||
///
|
|
||||||
/// If the token or its signature is invalid, it will return an error
|
|
||||||
pub fn decode<T: Deserialize>(token: &str, key: &[u8], algorithm: Algorithm) -> Result<TokenData<T>> {
|
|
||||||
internal_decode(token, key, algorithm, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decode a token into a struct containing Claims and Header
|
|
||||||
/// WARNING: this will not do any verification so only use that at your own risk
|
|
||||||
///
|
|
||||||
/// If the token is invalid, it will return an error
|
|
||||||
pub fn decode_without_verification<T: Deserialize>(token: &str, key: &[u8], algorithm: Algorithm) -> Result<TokenData<T>> {
|
|
||||||
internal_decode(token, key, algorithm, false)
|
|
||||||
}
|
|
||||||
|
|
|
@ -25,6 +25,8 @@ error_chain! {
|
||||||
display("Invalid Key")
|
display("Invalid Key")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validation error
|
||||||
|
|
||||||
/// When a token’s `exp` claim indicates that it has expired
|
/// When a token’s `exp` claim indicates that it has expired
|
||||||
ExpiredSignature {
|
ExpiredSignature {
|
||||||
description("expired signature")
|
description("expired signature")
|
||||||
|
@ -40,6 +42,11 @@ error_chain! {
|
||||||
description("invalid audience")
|
description("invalid audience")
|
||||||
display("Invalid Audience")
|
display("Invalid Audience")
|
||||||
}
|
}
|
||||||
|
/// When a token’s `aud` claim does not match one of the expected audience values
|
||||||
|
InvalidSubject {
|
||||||
|
description("invalid subject")
|
||||||
|
display("Invalid Subject")
|
||||||
|
}
|
||||||
/// When a token’s `iat` claim is in the future
|
/// When a token’s `iat` claim is in the future
|
||||||
InvalidIssuedAt {
|
InvalidIssuedAt {
|
||||||
description("invalid issued at")
|
description("invalid issued at")
|
||||||
|
|
|
@ -12,11 +12,13 @@ extern crate serde;
|
||||||
extern crate base64;
|
extern crate base64;
|
||||||
extern crate ring;
|
extern crate ring;
|
||||||
extern crate untrusted;
|
extern crate untrusted;
|
||||||
|
extern crate chrono;
|
||||||
|
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
mod header;
|
mod header;
|
||||||
mod crypto;
|
mod crypto;
|
||||||
mod serialization;
|
mod serialization;
|
||||||
|
mod validation;
|
||||||
|
|
||||||
pub use header::{Header};
|
pub use header::{Header};
|
||||||
pub use crypto::{
|
pub use crypto::{
|
||||||
|
@ -25,6 +27,5 @@ pub use crypto::{
|
||||||
verify,
|
verify,
|
||||||
encode,
|
encode,
|
||||||
decode,
|
decode,
|
||||||
decode_without_verification,
|
|
||||||
};
|
};
|
||||||
|
pub use validation::Validation;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use base64;
|
use base64;
|
||||||
use serde::de::Deserialize;
|
use serde::de::Deserialize;
|
||||||
use serde::ser::Serialize;
|
use serde::ser::Serialize;
|
||||||
use serde_json;
|
use serde_json::{from_str, to_string, Value};
|
||||||
|
use serde_json::map::Map;
|
||||||
|
|
||||||
use errors::{Result};
|
use errors::{Result};
|
||||||
use header::Header;
|
use header::Header;
|
||||||
|
@ -17,14 +17,24 @@ pub struct TokenData<T: Deserialize> {
|
||||||
|
|
||||||
/// Serializes to JSON and encodes to base64
|
/// Serializes to JSON and encodes to base64
|
||||||
pub fn to_jwt_part<T: Serialize>(input: &T) -> Result<String> {
|
pub fn to_jwt_part<T: Serialize>(input: &T) -> Result<String> {
|
||||||
let encoded = serde_json::to_string(input)?;
|
let encoded = to_string(input)?;
|
||||||
Ok(base64::encode_config(encoded.as_bytes(), base64::URL_SAFE_NO_PAD))
|
Ok(base64::encode_config(encoded.as_bytes(), base64::URL_SAFE_NO_PAD))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decodes from base64 and deserializes from JSON
|
/// Decodes from base64 and deserializes from JSON to a struct
|
||||||
pub fn from_jwt_part<B: AsRef<str>, T: Deserialize>(encoded: B) -> Result<T> {
|
pub fn from_jwt_part<B: AsRef<str>, T: Deserialize>(encoded: B) -> Result<T> {
|
||||||
let decoded = base64::decode_config(encoded.as_ref(), base64::URL_SAFE_NO_PAD)?;
|
let decoded = base64::decode_config(encoded.as_ref(), base64::URL_SAFE_NO_PAD)?;
|
||||||
let s = String::from_utf8(decoded)?;
|
let s = String::from_utf8(decoded)?;
|
||||||
|
|
||||||
Ok(serde_json::from_str(&s)?)
|
Ok(from_str(&s)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decodes from base64 and deserializes from JSON to a struct AND a hashmap
|
||||||
|
pub fn from_jwt_part_claims<B: AsRef<str>, T: Deserialize>(encoded: B) -> Result<(T, Map<String, Value>)> {
|
||||||
|
let decoded = base64::decode_config(encoded.as_ref(), base64::URL_SAFE_NO_PAD)?;
|
||||||
|
let s = String::from_utf8(decoded)?;
|
||||||
|
|
||||||
|
let claims: T = from_str(&s)?;
|
||||||
|
let map: Map<_,_> = from_str(&s)?;
|
||||||
|
Ok((claims, map))
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,313 @@
|
||||||
|
use chrono::UTC;
|
||||||
|
use serde::ser::Serialize;
|
||||||
|
use serde_json::{Value, from_value, to_value};
|
||||||
|
use serde_json::map::Map;
|
||||||
|
|
||||||
|
use errors::{Result, ErrorKind};
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct Validation {
|
||||||
|
pub leeway: i64,
|
||||||
|
pub validate_signature: bool,
|
||||||
|
pub validate_exp: bool,
|
||||||
|
pub validate_iat: bool,
|
||||||
|
pub validate_nbf: bool,
|
||||||
|
|
||||||
|
pub aud: Option<Value>,
|
||||||
|
pub iss: Option<String>,
|
||||||
|
pub sub: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Validation {
|
||||||
|
pub fn set_audience<T: Serialize>(&mut self, audience: &T) {
|
||||||
|
self.aud = Some(to_value(audience).unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Validation {
|
||||||
|
fn default() -> Validation {
|
||||||
|
Validation {
|
||||||
|
leeway: 0,
|
||||||
|
|
||||||
|
validate_signature: true,
|
||||||
|
|
||||||
|
validate_exp: true,
|
||||||
|
validate_iat: true,
|
||||||
|
validate_nbf: true,
|
||||||
|
|
||||||
|
iss: None,
|
||||||
|
sub: None,
|
||||||
|
aud: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
pub fn validate(claims: &Map<String, Value>, options: &Validation) -> Result<()> {
|
||||||
|
let now = UTC::now().timestamp();
|
||||||
|
|
||||||
|
if let Some(iat) = claims.get("iat") {
|
||||||
|
if options.validate_iat && from_value::<i64>(iat.clone())? > now + options.leeway {
|
||||||
|
return Err(ErrorKind::InvalidIssuedAt.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(exp) = claims.get("exp") {
|
||||||
|
if options.validate_exp && from_value::<i64>(exp.clone())? < now - options.leeway {
|
||||||
|
return Err(ErrorKind::ExpiredSignature.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(nbf) = claims.get("nbf") {
|
||||||
|
if options.validate_nbf && from_value::<i64>(nbf.clone())? > now + options.leeway {
|
||||||
|
return Err(ErrorKind::ImmatureSignature.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(iss) = claims.get("iss") {
|
||||||
|
if let Some(ref correct_iss) = options.iss {
|
||||||
|
if from_value::<String>(iss.clone())? != *correct_iss {
|
||||||
|
return Err(ErrorKind::InvalidIssuer.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(sub) = claims.get("sub") {
|
||||||
|
if let Some(ref correct_sub) = options.sub {
|
||||||
|
if from_value::<String>(sub.clone())? != *correct_sub {
|
||||||
|
return Err(ErrorKind::InvalidSubject.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(aud) = claims.get("aud") {
|
||||||
|
if let Some(ref correct_aud) = options.aud {
|
||||||
|
if aud != correct_aud {
|
||||||
|
return Err(ErrorKind::InvalidAudience.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use serde_json::{to_value};
|
||||||
|
use serde_json::map::Map;
|
||||||
|
use chrono::UTC;
|
||||||
|
|
||||||
|
use super::{validate, Validation};
|
||||||
|
|
||||||
|
use errors::ErrorKind;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn iat_in_past_ok() {
|
||||||
|
let mut claims = Map::new();
|
||||||
|
claims.insert("iat".to_string(), to_value(UTC::now().timestamp() - 10000).unwrap());
|
||||||
|
let res = validate(&claims, &Validation::default());
|
||||||
|
assert!(res.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn iat_in_future_fails() {
|
||||||
|
let mut claims = Map::new();
|
||||||
|
claims.insert("iat".to_string(), to_value(UTC::now().timestamp() + 100000).unwrap());
|
||||||
|
let res = validate(&claims, &Validation::default());
|
||||||
|
assert!(res.is_err());
|
||||||
|
|
||||||
|
match res.unwrap_err().kind() {
|
||||||
|
&ErrorKind::InvalidIssuedAt => (),
|
||||||
|
_ => assert!(false),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn iat_in_future_but_in_leeway_ok() {
|
||||||
|
let mut claims = Map::new();
|
||||||
|
claims.insert("iat".to_string(), to_value(UTC::now().timestamp() + 50).unwrap());
|
||||||
|
let validation = Validation {
|
||||||
|
leeway: 1000 * 60,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let res = validate(&claims, &validation);
|
||||||
|
assert!(res.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exp_in_future_ok() {
|
||||||
|
let mut claims = Map::new();
|
||||||
|
claims.insert("exp".to_string(), to_value(UTC::now().timestamp() + 10000).unwrap());
|
||||||
|
let res = validate(&claims, &Validation::default());
|
||||||
|
assert!(res.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exp_in_past_fails() {
|
||||||
|
let mut claims = Map::new();
|
||||||
|
claims.insert("exp".to_string(), to_value(UTC::now().timestamp() - 100000).unwrap());
|
||||||
|
let res = validate(&claims, &Validation::default());
|
||||||
|
assert!(res.is_err());
|
||||||
|
|
||||||
|
match res.unwrap_err().kind() {
|
||||||
|
&ErrorKind::ExpiredSignature => (),
|
||||||
|
_ => assert!(false),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exp_in_past_but_in_leeway_ok() {
|
||||||
|
let mut claims = Map::new();
|
||||||
|
claims.insert("exp".to_string(), to_value(UTC::now().timestamp() - 500).unwrap());
|
||||||
|
let validation = Validation {
|
||||||
|
leeway: 1000 * 60,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let res = validate(&claims, &validation);
|
||||||
|
assert!(res.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nbf_in_past_ok() {
|
||||||
|
let mut claims = Map::new();
|
||||||
|
claims.insert("nbf".to_string(), to_value(UTC::now().timestamp() - 10000).unwrap());
|
||||||
|
let res = validate(&claims, &Validation::default());
|
||||||
|
assert!(res.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nbf_in_future_fails() {
|
||||||
|
let mut claims = Map::new();
|
||||||
|
claims.insert("nbf".to_string(), to_value(UTC::now().timestamp() + 100000).unwrap());
|
||||||
|
let res = validate(&claims, &Validation::default());
|
||||||
|
assert!(res.is_err());
|
||||||
|
|
||||||
|
match res.unwrap_err().kind() {
|
||||||
|
&ErrorKind::ImmatureSignature => (),
|
||||||
|
_ => assert!(false),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nbf_in_future_but_in_leeway_ok() {
|
||||||
|
let mut claims = Map::new();
|
||||||
|
claims.insert("nbf".to_string(), to_value(UTC::now().timestamp() + 500).unwrap());
|
||||||
|
let validation = Validation {
|
||||||
|
leeway: 1000 * 60,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let res = validate(&claims, &validation);
|
||||||
|
assert!(res.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn iss_ok() {
|
||||||
|
let mut claims = Map::new();
|
||||||
|
claims.insert("iss".to_string(), to_value("Keats").unwrap());
|
||||||
|
let validation = Validation {
|
||||||
|
iss: Some("Keats".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let res = validate(&claims, &validation);
|
||||||
|
assert!(res.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn iss_not_matching_fails() {
|
||||||
|
let mut claims = Map::new();
|
||||||
|
claims.insert("iss".to_string(), to_value("Hacked").unwrap());
|
||||||
|
let validation = Validation {
|
||||||
|
iss: Some("Keats".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let res = validate(&claims, &validation);
|
||||||
|
assert!(res.is_err());
|
||||||
|
|
||||||
|
match res.unwrap_err().kind() {
|
||||||
|
&ErrorKind::InvalidIssuer => (),
|
||||||
|
_ => assert!(false),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sub_ok() {
|
||||||
|
let mut claims = Map::new();
|
||||||
|
claims.insert("sub".to_string(), to_value("Keats").unwrap());
|
||||||
|
let validation = Validation {
|
||||||
|
sub: Some("Keats".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let res = validate(&claims, &validation);
|
||||||
|
assert!(res.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sub_not_matching_fails() {
|
||||||
|
let mut claims = Map::new();
|
||||||
|
claims.insert("sub".to_string(), to_value("Hacked").unwrap());
|
||||||
|
let validation = Validation {
|
||||||
|
sub: Some("Keats".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let res = validate(&claims, &validation);
|
||||||
|
assert!(res.is_err());
|
||||||
|
|
||||||
|
match res.unwrap_err().kind() {
|
||||||
|
&ErrorKind::InvalidSubject => (),
|
||||||
|
_ => assert!(false),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn aud_string_ok() {
|
||||||
|
let mut claims = Map::new();
|
||||||
|
claims.insert("aud".to_string(), to_value("Everyone").unwrap());
|
||||||
|
let mut validation = Validation::default();
|
||||||
|
validation.set_audience(&"Everyone");
|
||||||
|
let res = validate(&claims, &validation);
|
||||||
|
assert!(res.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn aud_array_of_string_ok() {
|
||||||
|
let mut claims = Map::new();
|
||||||
|
claims.insert("aud".to_string(), to_value(["UserA", "UserB"]).unwrap());
|
||||||
|
let mut validation = Validation::default();
|
||||||
|
validation.set_audience(&["UserA", "UserB"]);
|
||||||
|
let res = validate(&claims, &validation);
|
||||||
|
assert!(res.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn aud_type_mismatch_fails() {
|
||||||
|
let mut claims = Map::new();
|
||||||
|
claims.insert("aud".to_string(), to_value("Everyone").unwrap());
|
||||||
|
let mut validation = Validation::default();
|
||||||
|
validation.set_audience(&["UserA", "UserB"]);
|
||||||
|
let res = validate(&claims, &validation);
|
||||||
|
assert!(res.is_err());
|
||||||
|
|
||||||
|
match res.unwrap_err().kind() {
|
||||||
|
&ErrorKind::InvalidAudience => (),
|
||||||
|
_ => assert!(false),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn aud_correct_type_not_matching_fails() {
|
||||||
|
let mut claims = Map::new();
|
||||||
|
claims.insert("aud".to_string(), to_value("Everyone").unwrap());
|
||||||
|
let mut validation = Validation::default();
|
||||||
|
validation.set_audience(&"None");
|
||||||
|
let res = validate(&claims, &validation);
|
||||||
|
assert!(res.is_err());
|
||||||
|
|
||||||
|
match res.unwrap_err().kind() {
|
||||||
|
&ErrorKind::InvalidAudience => (),
|
||||||
|
_ => assert!(false),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
18
tests/lib.rs
18
tests/lib.rs
|
@ -2,7 +2,7 @@ extern crate jsonwebtoken;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
|
|
||||||
use jsonwebtoken::{encode, decode, Algorithm, Header, sign, verify};
|
use jsonwebtoken::{encode, decode, Algorithm, Header, sign, verify, Validation};
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||||
|
@ -34,7 +34,7 @@ fn encode_with_custom_header() {
|
||||||
let mut header = Header::default();
|
let mut header = Header::default();
|
||||||
header.kid = Some("kid".to_string());
|
header.kid = Some("kid".to_string());
|
||||||
let token = encode(&header, &my_claims, "secret".as_ref()).unwrap();
|
let token = encode(&header, &my_claims, "secret".as_ref()).unwrap();
|
||||||
let token_data = decode::<Claims>(&token, "secret".as_ref(), Algorithm::HS256).unwrap();
|
let token_data = decode::<Claims>(&token, "secret".as_ref(), Algorithm::HS256, Validation::default()).unwrap();
|
||||||
assert_eq!(my_claims, token_data.claims);
|
assert_eq!(my_claims, token_data.claims);
|
||||||
assert_eq!("kid", token_data.header.kid.unwrap());
|
assert_eq!("kid", token_data.header.kid.unwrap());
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ fn round_trip_claim() {
|
||||||
company: "ACME".to_string()
|
company: "ACME".to_string()
|
||||||
};
|
};
|
||||||
let token = encode(&Header::default(), &my_claims, "secret".as_ref()).unwrap();
|
let token = encode(&Header::default(), &my_claims, "secret".as_ref()).unwrap();
|
||||||
let token_data = decode::<Claims>(&token, "secret".as_ref(), Algorithm::HS256).unwrap();
|
let token_data = decode::<Claims>(&token, "secret".as_ref(), Algorithm::HS256, Validation::default()).unwrap();
|
||||||
assert_eq!(my_claims, token_data.claims);
|
assert_eq!(my_claims, token_data.claims);
|
||||||
assert!(token_data.header.kid.is_none());
|
assert!(token_data.header.kid.is_none());
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ fn round_trip_claim() {
|
||||||
#[test]
|
#[test]
|
||||||
fn decode_token() {
|
fn decode_token() {
|
||||||
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.I1BvFoHe94AFf09O6tDbcSB8-jp8w6xZqmyHIwPeSdY";
|
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.I1BvFoHe94AFf09O6tDbcSB8-jp8w6xZqmyHIwPeSdY";
|
||||||
let claims = decode::<Claims>(token, "secret".as_ref(), Algorithm::HS256);
|
let claims = decode::<Claims>(token, "secret".as_ref(), Algorithm::HS256, Validation::default());
|
||||||
claims.unwrap();
|
claims.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ fn decode_token() {
|
||||||
#[should_panic(expected = "InvalidToken")]
|
#[should_panic(expected = "InvalidToken")]
|
||||||
fn decode_token_missing_parts() {
|
fn decode_token_missing_parts() {
|
||||||
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
|
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
|
||||||
let claims = decode::<Claims>(token, "secret".as_ref(), Algorithm::HS256);
|
let claims = decode::<Claims>(token, "secret".as_ref(), Algorithm::HS256, Validation::default());
|
||||||
claims.unwrap();
|
claims.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ fn decode_token_missing_parts() {
|
||||||
#[should_panic(expected = "InvalidSignature")]
|
#[should_panic(expected = "InvalidSignature")]
|
||||||
fn decode_token_invalid_signature() {
|
fn decode_token_invalid_signature() {
|
||||||
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.wrong";
|
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.wrong";
|
||||||
let claims = decode::<Claims>(token, "secret".as_ref(), Algorithm::HS256);
|
let claims = decode::<Claims>(token, "secret".as_ref(), Algorithm::HS256, Validation::default());
|
||||||
claims.unwrap();
|
claims.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,20 +78,20 @@ fn decode_token_invalid_signature() {
|
||||||
#[should_panic(expected = "WrongAlgorithmHeader")]
|
#[should_panic(expected = "WrongAlgorithmHeader")]
|
||||||
fn decode_token_wrong_algorithm() {
|
fn decode_token_wrong_algorithm() {
|
||||||
let token = "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.pKscJVk7-aHxfmQKlaZxh5uhuKhGMAa-1F5IX5mfUwI";
|
let token = "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.pKscJVk7-aHxfmQKlaZxh5uhuKhGMAa-1F5IX5mfUwI";
|
||||||
let claims = decode::<Claims>(token, "secret".as_ref(), Algorithm::HS256);
|
let claims = decode::<Claims>(token, "secret".as_ref(), Algorithm::HS256, Validation::default());
|
||||||
claims.unwrap();
|
claims.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn decode_token_with_bytes_secret() {
|
fn decode_token_with_bytes_secret() {
|
||||||
let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiY29tcGFueSI6Ikdvb2dvbCJ9.27QxgG96vpX4akKNpD1YdRGHE3_u2X35wR3EHA2eCrs";
|
let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiY29tcGFueSI6Ikdvb2dvbCJ9.27QxgG96vpX4akKNpD1YdRGHE3_u2X35wR3EHA2eCrs";
|
||||||
let claims = decode::<Claims>(token, b"\x01\x02\x03", Algorithm::HS256);
|
let claims = decode::<Claims>(token, b"\x01\x02\x03", Algorithm::HS256, Validation::default());
|
||||||
assert!(claims.is_ok());
|
assert!(claims.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn decode_token_with_shuffled_header_fields() {
|
fn decode_token_with_shuffled_header_fields() {
|
||||||
let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjb21wYW55IjoiMTIzNDU2Nzg5MCIsInN1YiI6IkpvaG4gRG9lIn0.SEIZ4Jg46VGhquuwPYDLY5qHF8AkQczF14aXM3a2c28";
|
let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjb21wYW55IjoiMTIzNDU2Nzg5MCIsInN1YiI6IkpvaG4gRG9lIn0.SEIZ4Jg46VGhquuwPYDLY5qHF8AkQczF14aXM3a2c28";
|
||||||
let claims = decode::<Claims>(token, "secret".as_ref(), Algorithm::HS256);
|
let claims = decode::<Claims>(token, "secret".as_ref(), Algorithm::HS256, Validation::default());
|
||||||
assert!(claims.is_ok());
|
assert!(claims.is_ok());
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ extern crate jsonwebtoken;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
|
|
||||||
use jsonwebtoken::{encode, decode, Algorithm, Header, sign, verify};
|
use jsonwebtoken::{encode, decode, Algorithm, Header, sign, verify, Validation};
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||||
|
@ -26,7 +26,7 @@ fn round_trip_claim() {
|
||||||
company: "ACME".to_string()
|
company: "ACME".to_string()
|
||||||
};
|
};
|
||||||
let token = encode(&Header::new(Algorithm::RS256), &my_claims, include_bytes!("private_rsa_key.der")).unwrap();
|
let token = encode(&Header::new(Algorithm::RS256), &my_claims, include_bytes!("private_rsa_key.der")).unwrap();
|
||||||
let token_data = decode::<Claims>(&token, include_bytes!("public_rsa_key.der"), Algorithm::RS256).unwrap();
|
let token_data = decode::<Claims>(&token, include_bytes!("public_rsa_key.der"), Algorithm::RS256, Validation::default()).unwrap();
|
||||||
assert_eq!(my_claims, token_data.claims);
|
assert_eq!(my_claims, token_data.claims);
|
||||||
assert!(token_data.header.kid.is_none());
|
assert!(token_data.header.kid.is_none());
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue