From d213fb8a6223265136497104cdf78e4fa20f29ce Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Fri, 25 Aug 2017 17:48:53 +0900 Subject: [PATCH 1/7] Remove validate_signature option and add decode_header --- CHANGELOG.md | 8 ++++++++ src/lib.rs | 22 ++++++++++++++++++++-- src/validation.rs | 8 -------- tests/lib.rs | 8 ++++---- 4 files changed, 32 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0c8ad8..30fd2ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 3.0.0 (unreleased) + +### Breaking change +- Remove `validate_signature` from `Validation` + +### Other +- Add `decode_header` to only decode the header: replaces the use case of `validate_signature` + ## 2.0.3 (2017-07-18) - Make `TokenData` public diff --git a/src/lib.rs b/src/lib.rs index 6014e12..39576d5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,7 +22,7 @@ mod crypto; mod serialization; mod validation; -pub use header::{Header}; +pub use header::Header; pub use crypto::{ Algorithm, sign, @@ -107,7 +107,7 @@ pub fn decode(token: &str, key: &[u8], validation: &Validat let (claims, header) = expect_two!(signing_input.rsplitn(2, '.')); let header: Header = from_jwt_part(header)?; - if validation.validate_signature && !verify(signature, signing_input, key, header.alg)? { + if !verify(signature, signing_input, key, header.alg)? { return Err(ErrorKind::InvalidSignature.into()); } @@ -123,3 +123,21 @@ pub fn decode(token: &str, key: &[u8], validation: &Validat Ok(TokenData { header: header, claims: decoded_claims }) } + +/// Decode a token and return the Header. This is not doing any kind of validation: it is meant to be +/// used when you don't know which `alg` the token is using and want to check +/// +/// If the token is invalid, it will return an error. +/// +/// ```rust,ignore +/// use jsonwebtoken::decode_header; +/// +/// +/// let token = "a.jwt.token".to_string(); +/// let header = decode_header(&token); +/// ``` +pub fn decode_header(token: &str) -> Result
{ + let (_, signing_input) = expect_two!(token.rsplitn(2, '.')); + let (_, header) = expect_two!(signing_input.rsplitn(2, '.')); + from_jwt_part(header) +} diff --git a/src/validation.rs b/src/validation.rs index d3bd4c0..34ebdd4 100644 --- a/src/validation.rs +++ b/src/validation.rs @@ -32,12 +32,6 @@ pub struct Validation { /// /// Defaults to `0`. pub leeway: i64, - /// Whether to actually validate the signature of the token. - /// - /// WARNING: only set that to false if you know what you are doing. - /// - /// Defaults to `true`. - pub validate_signature: bool, /// Whether to validate the `exp` field. /// /// It will return an error if the time in the `exp` field is past. @@ -93,8 +87,6 @@ impl Default for Validation { Validation { leeway: 0, - validate_signature: true, - validate_exp: true, validate_iat: true, validate_nbf: true, diff --git a/tests/lib.rs b/tests/lib.rs index bab1886..9844824 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -2,7 +2,7 @@ extern crate jsonwebtoken; #[macro_use] extern crate serde_derive; -use jsonwebtoken::{encode, decode, Algorithm, Header, sign, verify, Validation}; +use jsonwebtoken::{encode, decode, decode_header, Algorithm, Header, sign, verify, Validation}; #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] @@ -97,9 +97,9 @@ fn decode_token_with_shuffled_header_fields() { } #[test] -fn decode_without_validating_signature() { +fn decode_header_only() { let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjb21wYW55IjoiMTIzNDU2Nzg5MCIsInN1YiI6IkpvaG4gRG9lIn0.S"; - let claims = decode::(token, "secret".as_ref(), &Validation {validate_signature: false, ..Validation::default()}); - assert!(claims.is_ok()); + let header = decode_header(token).unwrap(); + assert_eq!(header.alg, Algorithm::HS256); } From 983380d1abad4077aeef62cecf9fda6673c047c7 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Fri, 25 Aug 2017 17:51:44 +0900 Subject: [PATCH 2/7] Make typ an Option in Header --- CHANGELOG.md | 1 + src/header.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30fd2ef..3b895dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Breaking change - Remove `validate_signature` from `Validation` +- Make `typ` optional in header, some providers apparently don't use it ### Other - Add `decode_header` to only decode the header: replaces the use case of `validate_signature` diff --git a/src/header.rs b/src/header.rs index 1aa049b..c055355 100644 --- a/src/header.rs +++ b/src/header.rs @@ -8,7 +8,7 @@ pub struct Header { /// The type of JWS: it can only be "JWT" here /// /// Defined in [RFC7515#4.1.9](https://tools.ietf.org/html/rfc7515#section-4.1.9). - typ: String, + typ: Option, /// The algorithm used /// /// Defined in [RFC7515#4.1.1](https://tools.ietf.org/html/rfc7515#section-4.1.1). @@ -44,7 +44,7 @@ impl Header { /// Returns a JWT header with the algorithm given pub fn new(algorithm: Algorithm) -> Header { Header { - typ: "JWT".to_string(), + typ: Some("JWT".to_string()), alg: algorithm, cty: None, jku: None, From 43a20030eef4b5ff96d46e7fad67f3a7aaf96d25 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Fri, 25 Aug 2017 17:53:32 +0900 Subject: [PATCH 3/7] Update docs --- README.md | 16 ++++++++++------ src/lib.rs | 1 - 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 476444e..46c448d 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ header. ### Encoding ```rust -let token = encode(&Header::default(), &my_claims, "secret".as_ref()).unwrap(); +let token = encode(&Header::default(), &my_claims, "secret".as_ref())?; ``` In that example, `my_claims` is an instance of a Claims struct that derives `Serialize` and `Deserialize`. The default algorithm is HS256. @@ -37,7 +37,7 @@ Look at custom headers section to see how to change that. ### Decoding ```rust -let token = decode::(&token, "secret", &Validation::default()).unwrap(); +let token = decode::(&token, "secret", &Validation::default())?; // token is a struct with 2 params: header and claims ``` `decode` can error for a variety of reasons: @@ -46,6 +46,13 @@ let token = decode::(&token, "secret", &Validation::default()).unwrap(); - error while decoding base64 or the result of decoding base64 is not valid UTF-8 - validation of at least one reserved claim failed +In some cases, you will want to only decode the header: + + +```rust +let header = decode_header(&token)?; +``` + ### Validation This library validates automatically the `iat`, `exp` and `nbf` claims if found. You can also validate the `sub`, `iss` and `aud` but those require setting the expected value. @@ -69,9 +76,6 @@ validation.set_audience(&["Me", "You"]); // array of strings let mut validation = Validation {algorithms: Some(vec![Algorithm::HS256]), ..Default::default()}; ``` -It's also possible to disable verifying the signature of a token by setting the `validate_signature` to `false`. This should -only be done if you know what you are doing. - ### Custom headers All the parameters from the RFC are supported but the default header only has `typ` and `alg` set: all the other fields are optional. If you want to set the `kid` parameter for example: @@ -80,7 +84,7 @@ If you want to set the `kid` parameter for example: let mut header = Header::default(); header.kid = Some("blabla".to_owned()); header.alg = Algorithm::HS512; -let token = encode(&header, &my_claims, "secret".as_ref()).unwrap(); +let token = encode(&header, &my_claims, "secret".as_ref())?; ``` Look at `examples/custom_header.rs` for a full working example. diff --git a/src/lib.rs b/src/lib.rs index 39576d5..6ddb704 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -132,7 +132,6 @@ pub fn decode(token: &str, key: &[u8], validation: &Validat /// ```rust,ignore /// use jsonwebtoken::decode_header; /// -/// /// let token = "a.jwt.token".to_string(); /// let header = decode_header(&token); /// ``` From 5839cb45527132efff559b2f29fea3b201bdac0a Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Sat, 26 Aug 2017 09:54:20 +0900 Subject: [PATCH 4/7] Don't serialize typ if None --- src/header.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/header.rs b/src/header.rs index c055355..a2dc921 100644 --- a/src/header.rs +++ b/src/header.rs @@ -8,6 +8,7 @@ pub struct Header { /// The type of JWS: it can only be "JWT" here /// /// Defined in [RFC7515#4.1.9](https://tools.ietf.org/html/rfc7515#section-4.1.9). + #[serde(skip_serializing_if = "Option::is_none")] typ: Option, /// The algorithm used /// From 7e36d3f7bbec9ca58243bc78420eade0f6d7f38c Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Wed, 30 Aug 2017 18:09:57 +0900 Subject: [PATCH 5/7] Update ring and fix leeway docs --- CHANGELOG.md | 5 +++++ Cargo.toml | 2 +- README.md | 4 ++-- src/validation.rs | 4 ++-- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b895dd..1c29c71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ - Remove `validate_signature` from `Validation` - Make `typ` optional in header, some providers apparently don't use it +### Others + +- Update ring +- Fix documentation about `leeway` being in seconds and not milliseconds + ### Other - Add `decode_header` to only decode the header: replaces the use case of `validate_signature` diff --git a/Cargo.toml b/Cargo.toml index 87b3e24..de0817c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ error-chain = { version = "0.10", default-features = false } serde_json = "1.0" serde_derive = "1.0" serde = "1.0" -ring = { version = "0.11.0", features = ["rsa_signing", "dev_urandom_fallback"] } +ring = { version = "0.12.0", features = ["rsa_signing", "dev_urandom_fallback"] } base64 = "0.6" untrusted = "0.5" chrono = "0.4" diff --git a/README.md b/README.md index 46c448d..6c8fac5 100644 --- a/README.md +++ b/README.md @@ -64,8 +64,8 @@ use jsonwebtoken::{Validation, Algorithm}; // Default valuation let validation = Validation::default(); -// Adding some leeway (in ms) for iat, exp and nbf checks -let mut validation = Validation {leeway: 1000 * 60, ..Default::default()}; +// Adding some leeway (in seconds) for iat, exp and nbf checks +let mut validation = Validation {leeway: 60, ..Default::default()}; // Checking issuer let mut validation = Validation {iss: Some("issuer".to_string()), ..Default::default()}; // Setting audience diff --git a/src/validation.rs b/src/validation.rs index 34ebdd4..e1465ad 100644 --- a/src/validation.rs +++ b/src/validation.rs @@ -18,7 +18,7 @@ use crypto::Algorithm; /// let validation = Validation::default(); /// /// // Changing one parameter -/// let mut validation = Validation {leeway: 1000 * 60, ..Default::default()}; +/// let mut validation = Validation {leeway: 60, ..Default::default()}; /// /// // Setting audience /// let mut validation = Validation::default(); @@ -27,7 +27,7 @@ use crypto::Algorithm; /// ``` #[derive(Debug, Clone, PartialEq)] pub struct Validation { - /// Add some leeway (in ms) to the `exp`, `iat` and `nbf` validation to + /// Add some leeway (in seconds) to the `exp`, `iat` and `nbf` validation to /// account for clock skew. /// /// Defaults to `0`. From 3985915da6918a6ce225e2ff58959a3f4d2d2533 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Thu, 7 Sep 2017 16:46:40 +0900 Subject: [PATCH 6/7] Update error-chain and make typ public --- CHANGELOG.md | 6 ++---- Cargo.toml | 6 +++--- src/header.rs | 2 +- tests/lib.rs | 1 + 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c29c71..adbb7f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,15 +3,13 @@ ## 3.0.0 (unreleased) ### Breaking change -- Remove `validate_signature` from `Validation` +- Remove `validate_signature` from `Validation`, use `decode_header` instead if you don't know the alg used - Make `typ` optional in header, some providers apparently don't use it ### Others -- Update ring +- Update ring & error-chain - Fix documentation about `leeway` being in seconds and not milliseconds - -### Other - Add `decode_header` to only decode the header: replaces the use case of `validate_signature` ## 2.0.3 (2017-07-18) diff --git a/Cargo.toml b/Cargo.toml index de0817c..8058205 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "jsonwebtoken" -version = "2.0.3" -authors = ["Vincent Prouillet "] +version = "3.0.0" +authors = ["Vincent Prouillet "] license = "MIT" readme = "README.md" description = "Create and parse JWT in a strongly typed way." @@ -10,7 +10,7 @@ repository = "https://github.com/Keats/rust-jwt" keywords = ["jwt", "web", "api", "token", "json"] [dependencies] -error-chain = { version = "0.10", default-features = false } +error-chain = { version = "0.11", default-features = false } serde_json = "1.0" serde_derive = "1.0" serde = "1.0" diff --git a/src/header.rs b/src/header.rs index a2dc921..042149c 100644 --- a/src/header.rs +++ b/src/header.rs @@ -9,7 +9,7 @@ pub struct Header { /// /// Defined in [RFC7515#4.1.9](https://tools.ietf.org/html/rfc7515#section-4.1.9). #[serde(skip_serializing_if = "Option::is_none")] - typ: Option, + pub typ: Option, /// The algorithm used /// /// Defined in [RFC7515#4.1.1](https://tools.ietf.org/html/rfc7515#section-4.1.1). diff --git a/tests/lib.rs b/tests/lib.rs index 9844824..185cd6b 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -101,5 +101,6 @@ fn decode_header_only() { let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjb21wYW55IjoiMTIzNDU2Nzg5MCIsInN1YiI6IkpvaG4gRG9lIn0.S"; let header = decode_header(token).unwrap(); assert_eq!(header.alg, Algorithm::HS256); + assert_eq!(header.typ, Some("JWT".to_string())); } From 4a656ffda7ed07b19fb09140dfdc22ee2ca99e14 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Fri, 8 Sep 2017 15:36:52 +0900 Subject: [PATCH 7/7] Update README.md --- README.md | 66 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 6c8fac5..c687c12 100644 --- a/README.md +++ b/README.md @@ -8,32 +8,47 @@ Add the following to Cargo.toml: ```toml -jsonwebtoken = "2" -serde_derive = "1.0" +jsonwebtoken = "3" +serde_derive = "1" ``` ## How to use -There is a complete example in `examples/claims.rs` but here's a quick one. +Complete examples are available in the examples directory: a basic one and one with a custom header. -In terms of imports: +In terms of imports and structs: ```rust extern crate jsonwebtoken as jwt; #[macro_use] extern crate serde_derive; use jwt::{encode, decode, Header, Algorithm, Validation}; + +/// Our claims struct, it needs to derive `Serialize` and/or `Deserialize` +#[derive(Debug, Serialize, Deserialize)] +struct Claims { + sub: String, + company: String +} ``` -Look at the examples directory for 2 examples: a basic one and one with a custom -header. - ### Encoding +The default algorithm is HS256. + ```rust let token = encode(&Header::default(), &my_claims, "secret".as_ref())?; ``` -In that example, `my_claims` is an instance of a Claims struct that derives `Serialize` and `Deserialize`. -The default algorithm is HS256. -Look at custom headers section to see how to change that. + +#### Custom headers & changing algorithm +All the parameters from the RFC are supported but the default header only has `typ` and `alg` set. +If you want to set the `kid` parameter or change the algorithm for example: + +```rust +let mut header = Header::default(); +header.kid = Some("blabla".to_owned()); +header.alg = Algorithm::HS512; +let token = encode(&header, &my_claims, "secret".as_ref())?; +``` +Look at `examples/custom_header.rs` for a full working example. ### Decoding ```rust @@ -46,18 +61,23 @@ let token = decode::(&token, "secret", &Validation::default())?; - error while decoding base64 or the result of decoding base64 is not valid UTF-8 - validation of at least one reserved claim failed -In some cases, you will want to only decode the header: - +In some cases, for example if you don't know the algorithm used, you will want to only decode the header: ```rust let header = decode_header(&token)?; ``` -### Validation -This library validates automatically the `iat`, `exp` and `nbf` claims if found. You can also validate the `sub`, `iss` and `aud` but -those require setting the expected value. -You can add some leeway to the `iat`, `exp` and `nbf` validation by setting the `leeway` parameter as shown in the example below as well -as select allowed algorithms. +This does not perform any validation on the token. + +#### Validation +This library validates automatically the `iat`, `exp` and `nbf` claims if present. You can also validate the `sub`, `iss` and `aud` but +those require setting the expected value in the `Validation` struct. + +Since validating time fields is always a bit tricky due to clock skew, +you can add some leeway to the `iat`, `exp` and `nbf` validation by setting a `leeway` parameter. + +Last but not least, if you are not using HS256 for the algorithm, you will need to update the `algorithms` field of the `Validation` struct +to the one you are using. ```rust use jsonwebtoken::{Validation, Algorithm}; @@ -76,18 +96,6 @@ validation.set_audience(&["Me", "You"]); // array of strings let mut validation = Validation {algorithms: Some(vec![Algorithm::HS256]), ..Default::default()}; ``` -### Custom headers -All the parameters from the RFC are supported but the default header only has `typ` and `alg` set: all the other fields are optional. -If you want to set the `kid` parameter for example: - -```rust -let mut header = Header::default(); -header.kid = Some("blabla".to_owned()); -header.alg = Algorithm::HS512; -let token = encode(&header, &my_claims, "secret".as_ref())?; -``` -Look at `examples/custom_header.rs` for a full working example. - ## Algorithms This library currently supports the following: