Merge pull request #39 from Keats/v3

V3
This commit is contained in:
Vincent Prouillet 2017-09-08 15:51:26 +09:00 committed by GitHub
commit df9449caae
7 changed files with 92 additions and 57 deletions

View File

@ -1,5 +1,17 @@
# Changelog
## 3.0.0 (unreleased)
### Breaking change
- 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 & error-chain
- Fix documentation about `leeway` being in seconds and not milliseconds
- Add `decode_header` to only decode the header: replaces the use case of `validate_signature`
## 2.0.3 (2017-07-18)
- Make `TokenData` public

View File

@ -1,7 +1,7 @@
[package]
name = "jsonwebtoken"
version = "2.0.3"
authors = ["Vincent Prouillet <vincent@wearewizards.io>"]
version = "3.0.0"
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"]
license = "MIT"
readme = "README.md"
description = "Create and parse JWT in a strongly typed way."
@ -10,11 +10,11 @@ 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"
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"

View File

@ -8,36 +8,51 @@
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};
```
Look at the examples directory for 2 examples: a basic one and one with a custom
header.
/// Our claims struct, it needs to derive `Serialize` and/or `Deserialize`
#[derive(Debug, Serialize, Deserialize)]
struct Claims {
sub: String,
company: String
}
```
### Encoding
```rust
let token = encode(&Header::default(), &my_claims, "secret".as_ref()).unwrap();
```
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.
```rust
let token = encode(&Header::default(), &my_claims, "secret".as_ref())?;
```
#### 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
let token = decode::<Claims>(&token, "secret", &Validation::default()).unwrap();
let token = decode::<Claims>(&token, "secret", &Validation::default())?;
// token is a struct with 2 params: header and claims
```
`decode` can error for a variety of reasons:
@ -46,19 +61,31 @@ let token = decode::<Claims>(&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
### 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.
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)?;
```
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};
// 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
@ -69,21 +96,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:
```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()).unwrap();
```
Look at `examples/custom_header.rs` for a full working example.
## Algorithms
This library currently supports the following:

View File

@ -8,7 +8,8 @@ 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,
#[serde(skip_serializing_if = "Option::is_none")]
pub typ: Option<String>,
/// The algorithm used
///
/// Defined in [RFC7515#4.1.1](https://tools.ietf.org/html/rfc7515#section-4.1.1).
@ -44,7 +45,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,

View File

@ -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<T: DeserializeOwned>(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,20 @@ pub fn decode<T: DeserializeOwned>(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<Header> {
let (_, signing_input) = expect_two!(token.rsplitn(2, '.'));
let (_, header) = expect_two!(signing_input.rsplitn(2, '.'));
from_jwt_part(header)
}

View File

@ -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,17 +27,11 @@ 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`.
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,

View File

@ -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,10 @@ 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::<Claims>(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);
assert_eq!(header.typ, Some("JWT".to_string()));
}