feat: configurable validation (validation: iss, aud, exp, nbf, leeway) (fixes #1) (#4)

This commit is contained in:
cduvray 2023-02-26 20:30:55 +01:00 committed by GitHub
parent 28c7eedcd5
commit 683f932468
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 471 additions and 75 deletions

View file

@ -5,11 +5,15 @@ JWT authorizer Layer for Axum.
## Features ## Features
- JWT token verification (Bearer) - JWT token verification (Bearer)
- Algoritms: ECDSA, RSA, EdDSA, HS - Algoritms: ECDSA, RSA, EdDSA, HMAC
- JWKS endpoint support - JWKS endpoint support
- Configurable refresh - Configurable refresh
- OpenId Connect Discovery
- Validation
- exp, nbf, iss, aud
- Claims extraction - Claims extraction
- Claims checker - Claims checker
- Tracing support (error logging)
## Usage ## Usage
@ -17,7 +21,7 @@ See documentation of the [`jwt-authorizer`](./jwt-authorizer/docs/README.md) mod
## Development ## Development
... Minimum supported Rust version is 1.65.
## Contributing ## Contributing

View file

@ -1,5 +1,10 @@
# Key generation # Key generation
## RSA
> openssl genrsa -out rsa-private2.pem 1024
> openssl rsa -in rsa-private2.pem -out rsa-public2.pem -pubout -outform PEM
## EC (ECDSA) - (algorigthm ES256 - ECDSA using SHA-256) ## EC (ECDSA) - (algorigthm ES256 - ECDSA using SHA-256)
curve name: prime256v1 (secp256r1, secp384r1) curve name: prime256v1 (secp256r1, secp384r1)

9
config/rsa-public2.pem Normal file
View file

@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyRE6rHuNR0QbHO3H3Kt2
pOKGVhQqGZXInOduQNxXzuKlvQTLUTv4l4sggh5/CYYi/cvI+SXVT9kPWSKXxJXB
Xd/4LkvcPuUakBoAkfh+eiFVMh2VrUyWyj3MFl0HTVF9KwRXLAcwkREiS3npThHR
yIxuy0ZMeZfxVL5arMhw1SRELB8HoGfG/AtH89BIE9jDBHZ9dLelK9a184zAf8Lw
oPLxvJb3Il5nncqPcSfKDDodMFBIMc4lQzDKL5gvmiXLXB1AGLm8KBjfE8s3L5xq
i+yUod+j8MtvIj812dkS4QMiRVN/by2h3ZY8LYVGrqZXZTcgn2ujn8uKjXLZVD5T
dQIDAQAB
-----END PUBLIC KEY-----

View file

@ -101,6 +101,7 @@ fn build_header(alg: Algorithm, kid: &str) -> Header {
struct Claims { struct Claims {
iss: &'static str, iss: &'static str,
sub: &'static str, sub: &'static str,
aud: &'static str,
exp: usize, exp: usize,
nbf: usize, nbf: usize,
} }
@ -110,6 +111,7 @@ pub async fn tokens() -> Json<Value> {
let claims = Claims { let claims = Claims {
iss: ISSUER_URI, iss: ISSUER_URI,
sub: "b@b.com", sub: "b@b.com",
aud: "aud1",
exp: 2000000000, // May 2033 exp: 2000000000, // May 2033
nbf: 1516239022, // Jan 2018 nbf: 1516239022, // Jan 2018
}; };

View file

@ -5,13 +5,15 @@ JWT authoriser Layer for Axum.
## Features ## Features
- JWT token verification (Bearer) - JWT token verification (Bearer)
- Algoritms: ECDSA, RSA, EdDSA, HS - Algoritms: ECDSA, RSA, EdDSA, HMAC
- JWKS endpoint support - JWKS endpoint support
- Configurable refresh - Configurable refresh
- OpenId Connect Discovery - OpenId Connect Discovery
- Validation
- exp, nbf, iss, aud
- Claims extraction - Claims extraction
- Claims checker - Claims checker
- tracing support (error logging) - Tracing support (error logging)
## Usage Example ## Usage Example
@ -48,6 +50,30 @@ JWT authoriser Layer for Axum.
# }; # };
``` ```
## Validation
Validation configuration object.
If no validation configuration is provided default values will be applyed.
docs: [`jwt-authorizer::Validation`]
```rust
# use jwt_authorizer::{JwtAuthorizer, Validation};
# use serde_json::Value;
let validation = Validation::new()
.iss(&["https://issuer1", "https://issuer2"])
.aud(&["audience1"])
.nbf(true)
.leeway(20);
let jwt_auth: JwtAuthorizer<Value> = JwtAuthorizer::from_oidc("https://accounts.google.com")
.validation(validation);
```
## ClaimsChecker ## ClaimsChecker
A check function (mapping deserialized claims to boolean) can be added to the authorizer. A check function (mapping deserialized claims to boolean) can be added to the authorizer.

View file

@ -1,6 +1,6 @@
use std::io::Read; use std::io::Read;
use jsonwebtoken::{decode, decode_header, jwk::JwkSet, DecodingKey, TokenData, Validation}; use jsonwebtoken::{decode, decode_header, jwk::JwkSet, DecodingKey, TokenData};
use reqwest::Url; use reqwest::Url;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
@ -37,6 +37,7 @@ where
{ {
pub key_source: KeySource, pub key_source: KeySource,
pub claims_checker: Option<FnClaimsChecker<C>>, pub claims_checker: Option<FnClaimsChecker<C>>,
pub validation: crate::validation::Validation,
} }
fn read_data(path: &str) -> Result<Vec<u8>, InitError> { fn read_data(path: &str) -> Result<Vec<u8>, InitError> {
@ -64,6 +65,7 @@ where
key_source_type: &KeySourceType, key_source_type: &KeySourceType,
claims_checker: Option<FnClaimsChecker<C>>, claims_checker: Option<FnClaimsChecker<C>>,
refresh: Option<Refresh>, refresh: Option<Refresh>,
validation: crate::validation::Validation,
) -> Result<Authorizer<C>, InitError> { ) -> Result<Authorizer<C>, InitError> {
Ok(match key_source_type { Ok(match key_source_type {
KeySourceType::RSA(path) => { KeySourceType::RSA(path) => {
@ -71,6 +73,7 @@ where
Authorizer { Authorizer {
key_source: KeySource::DecodingKeySource(key), key_source: KeySource::DecodingKeySource(key),
claims_checker, claims_checker,
validation,
} }
} }
KeySourceType::EC(path) => { KeySourceType::EC(path) => {
@ -78,6 +81,7 @@ where
Authorizer { Authorizer {
key_source: KeySource::DecodingKeySource(key), key_source: KeySource::DecodingKeySource(key),
claims_checker, claims_checker,
validation,
} }
} }
KeySourceType::ED(path) => { KeySourceType::ED(path) => {
@ -85,6 +89,7 @@ where
Authorizer { Authorizer {
key_source: KeySource::DecodingKeySource(key), key_source: KeySource::DecodingKeySource(key),
claims_checker, claims_checker,
validation,
} }
} }
KeySourceType::Secret(secret) => { KeySourceType::Secret(secret) => {
@ -92,6 +97,7 @@ where
Authorizer { Authorizer {
key_source: KeySource::DecodingKeySource(key), key_source: KeySource::DecodingKeySource(key),
claims_checker, claims_checker,
validation,
} }
} }
KeySourceType::JwksString(jwks_str) => { KeySourceType::JwksString(jwks_str) => {
@ -102,6 +108,7 @@ where
Authorizer { Authorizer {
key_source: KeySource::DecodingKeySource(k), key_source: KeySource::DecodingKeySource(k),
claims_checker, claims_checker,
validation,
} }
} }
KeySourceType::Jwks(url) => { KeySourceType::Jwks(url) => {
@ -110,6 +117,7 @@ where
Authorizer { Authorizer {
key_source: KeySource::KeyStoreSource(key_store_manager), key_source: KeySource::KeyStoreSource(key_store_manager),
claims_checker, claims_checker,
validation,
} }
} }
KeySourceType::Discovery(issuer_url) => { KeySourceType::Discovery(issuer_url) => {
@ -120,6 +128,7 @@ where
Authorizer { Authorizer {
key_source: KeySource::KeyStoreSource(key_store_manager), key_source: KeySource::KeyStoreSource(key_store_manager),
claims_checker, claims_checker,
validation,
} }
} }
}) })
@ -127,9 +136,11 @@ where
pub async fn check_auth(&self, token: &str) -> Result<TokenData<C>, AuthError> { pub async fn check_auth(&self, token: &str) -> Result<TokenData<C>, AuthError> {
let header = decode_header(token)?; let header = decode_header(token)?;
let validation = Validation::new(header.alg); // TODO: build validation only once or cache it (store it in key_source?)
// (problem: alg family is checked in jsonwebtoken but may change with store refresh)
let jwt_validation = &self.validation.to_jwt_validation(header.alg);
let decoding_key = self.key_source.get_key(header).await?; let decoding_key = self.key_source.get_key(header).await?;
let token_data = decode::<C>(token, &decoding_key, &validation)?; let token_data = decode::<C>(token, &decoding_key, jwt_validation)?;
if let Some(ref checker) = self.claims_checker { if let Some(ref checker) = self.claims_checker {
if !checker.check(&token_data.claims) { if !checker.check(&token_data.claims) {
@ -147,12 +158,14 @@ mod tests {
use jsonwebtoken::{Algorithm, Header}; use jsonwebtoken::{Algorithm, Header};
use serde_json::Value; use serde_json::Value;
use crate::validation::Validation;
use super::{Authorizer, KeySourceType}; use super::{Authorizer, KeySourceType};
#[tokio::test] #[tokio::test]
async fn build_from_secret() { async fn build_from_secret() {
let h = Header::new(Algorithm::HS256); let h = Header::new(Algorithm::HS256);
let a = Authorizer::<Value>::build(&KeySourceType::Secret("xxxxxx"), None, None) let a = Authorizer::<Value>::build(&KeySourceType::Secret("xxxxxx"), None, None, Validation::new())
.await .await
.unwrap(); .unwrap();
let k = a.key_source.get_key(h); let k = a.key_source.get_key(h);
@ -171,7 +184,7 @@ mod tests {
"e": "AQAB" "e": "AQAB"
}]} }]}
"#; "#;
let a = Authorizer::<Value>::build(&KeySourceType::JwksString(jwks.to_owned()), None, None) let a = Authorizer::<Value>::build(&KeySourceType::JwksString(jwks.to_owned()), None, None, Validation::new())
.await .await
.unwrap(); .unwrap();
let k = a.key_source.get_key(Header::new(Algorithm::RS256)); let k = a.key_source.get_key(Header::new(Algorithm::RS256));
@ -180,19 +193,34 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn build_from_file() { async fn build_from_file() {
let a = Authorizer::<Value>::build(&KeySourceType::RSA("../config/rsa-public1.pem".to_owned()), None, None) let a = Authorizer::<Value>::build(
&KeySourceType::RSA("../config/rsa-public1.pem".to_owned()),
None,
None,
Validation::new(),
)
.await .await
.unwrap(); .unwrap();
let k = a.key_source.get_key(Header::new(Algorithm::RS256)); let k = a.key_source.get_key(Header::new(Algorithm::RS256));
assert!(k.await.is_ok()); assert!(k.await.is_ok());
let a = Authorizer::<Value>::build(&KeySourceType::EC("../config/ecdsa-public1.pem".to_owned()), None, None) let a = Authorizer::<Value>::build(
&KeySourceType::EC("../config/ecdsa-public1.pem".to_owned()),
None,
None,
Validation::new(),
)
.await .await
.unwrap(); .unwrap();
let k = a.key_source.get_key(Header::new(Algorithm::ES256)); let k = a.key_source.get_key(Header::new(Algorithm::ES256));
assert!(k.await.is_ok()); assert!(k.await.is_ok());
let a = Authorizer::<Value>::build(&KeySourceType::ED("../config/ed25519-public1.pem".to_owned()), None, None) let a = Authorizer::<Value>::build(
&KeySourceType::ED("../config/ed25519-public1.pem".to_owned()),
None,
None,
Validation::new(),
)
.await .await
.unwrap(); .unwrap();
let k = a.key_source.get_key(Header::new(Algorithm::EdDSA)); let k = a.key_source.get_key(Header::new(Algorithm::EdDSA));
@ -201,21 +229,34 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn build_file_errors() { async fn build_file_errors() {
let a = Authorizer::<Value>::build(&KeySourceType::RSA("./config/does-not-exist.pem".to_owned()), None, None).await; let a = Authorizer::<Value>::build(
&KeySourceType::RSA("./config/does-not-exist.pem".to_owned()),
None,
None,
Validation::new(),
)
.await;
println!("{:?}", a.as_ref().err()); println!("{:?}", a.as_ref().err());
assert!(a.is_err()); assert!(a.is_err());
} }
#[tokio::test] #[tokio::test]
async fn build_jwks_url_error() { async fn build_jwks_url_error() {
let a = Authorizer::<Value>::build(&KeySourceType::Jwks("://xxxx".to_owned()), None, None).await; let a =
Authorizer::<Value>::build(&KeySourceType::Jwks("://xxxx".to_owned()), None, None, Validation::default()).await;
println!("{:?}", a.as_ref().err()); println!("{:?}", a.as_ref().err());
assert!(a.is_err()); assert!(a.is_err());
} }
#[tokio::test] #[tokio::test]
async fn build_discovery_url_error() { async fn build_discovery_url_error() {
let a = Authorizer::<Value>::build(&KeySourceType::Discovery("://xxxx".to_owned()), None, None).await; let a = Authorizer::<Value>::build(
&KeySourceType::Discovery("://xxxx".to_owned()),
None,
None,
Validation::default(),
)
.await;
println!("{:?}", a.as_ref().err()); println!("{:?}", a.as_ref().err());
assert!(a.is_err()); assert!(a.is_err());
} }

View file

@ -17,6 +17,7 @@ use tower_service::Service;
use crate::authorizer::{Authorizer, FnClaimsChecker, KeySourceType}; use crate::authorizer::{Authorizer, FnClaimsChecker, KeySourceType};
use crate::error::InitError; use crate::error::InitError;
use crate::jwks::key_store_manager::Refresh; use crate::jwks::key_store_manager::Refresh;
use crate::validation::Validation;
use crate::{AuthError, RefreshStrategy}; use crate::{AuthError, RefreshStrategy};
/// Authorizer Layer builder /// Authorizer Layer builder
@ -30,6 +31,7 @@ where
key_source_type: KeySourceType, key_source_type: KeySourceType,
refresh: Option<Refresh>, refresh: Option<Refresh>,
claims_checker: Option<FnClaimsChecker<C>>, claims_checker: Option<FnClaimsChecker<C>>,
validation: Option<Validation>,
} }
/// authorization layer builder /// authorization layer builder
@ -43,6 +45,7 @@ where
key_source_type: KeySourceType::Discovery(issuer.to_string()), key_source_type: KeySourceType::Discovery(issuer.to_string()),
refresh: Default::default(), refresh: Default::default(),
claims_checker: None, claims_checker: None,
validation: None,
} }
} }
@ -52,6 +55,7 @@ where
key_source_type: KeySourceType::Jwks(url.to_owned()), key_source_type: KeySourceType::Jwks(url.to_owned()),
refresh: Default::default(), refresh: Default::default(),
claims_checker: None, claims_checker: None,
validation: None,
} }
} }
@ -61,6 +65,7 @@ where
key_source_type: KeySourceType::RSA(path.to_owned()), key_source_type: KeySourceType::RSA(path.to_owned()),
refresh: Default::default(), refresh: Default::default(),
claims_checker: None, claims_checker: None,
validation: None,
} }
} }
@ -70,6 +75,7 @@ where
key_source_type: KeySourceType::EC(path.to_owned()), key_source_type: KeySourceType::EC(path.to_owned()),
refresh: Default::default(), refresh: Default::default(),
claims_checker: None, claims_checker: None,
validation: None,
} }
} }
@ -79,6 +85,7 @@ where
key_source_type: KeySourceType::ED(path.to_owned()), key_source_type: KeySourceType::ED(path.to_owned()),
refresh: Default::default(), refresh: Default::default(),
claims_checker: None, claims_checker: None,
validation: None,
} }
} }
@ -88,6 +95,7 @@ where
key_source_type: KeySourceType::Secret(secret), key_source_type: KeySourceType::Secret(secret),
refresh: Default::default(), refresh: Default::default(),
claims_checker: None, claims_checker: None,
validation: None,
} }
} }
@ -120,9 +128,16 @@ where
self self
} }
pub fn validation(mut self, validation: Validation) -> JwtAuthorizer<C> {
self.validation = Some(validation);
self
}
/// Build axum layer /// Build axum layer
pub async fn layer(&self) -> Result<AsyncAuthorizationLayer<C>, InitError> { pub async fn layer(self) -> Result<AsyncAuthorizationLayer<C>, InitError> {
let auth = Arc::new(Authorizer::build(&self.key_source_type, self.claims_checker.clone(), self.refresh).await?); let val = self.validation.unwrap_or_default();
let auth = Arc::new(Authorizer::build(&self.key_source_type, self.claims_checker, self.refresh, val).await?);
Ok(AsyncAuthorizationLayer::new(auth)) Ok(AsyncAuthorizationLayer::new(auth))
} }
} }

View file

@ -8,12 +8,14 @@ use serde::de::DeserializeOwned;
pub use self::error::AuthError; pub use self::error::AuthError;
pub use jwks::key_store_manager::{Refresh, RefreshStrategy}; pub use jwks::key_store_manager::{Refresh, RefreshStrategy};
pub use layer::JwtAuthorizer; pub use layer::JwtAuthorizer;
pub use validation::Validation;
pub mod authorizer; pub mod authorizer;
pub mod error; pub mod error;
pub mod jwks; pub mod jwks;
pub mod layer; pub mod layer;
mod oidc; mod oidc;
pub mod validation;
/// Claims serialized using T /// Claims serialized using T
#[derive(Debug, Clone, Copy, Default)] #[derive(Debug, Clone, Copy, Default)]

View file

@ -0,0 +1,129 @@
use std::collections::HashSet;
use jsonwebtoken::Algorithm;
/// Defines the jwt validation parameters (with defaults simplifying configuration).
pub struct Validation {
/// Add some leeway (in seconds) to the `exp` and `nbf` validation to
/// account for clock skew.
///
/// Defaults to `60`.
pub leeway: u64,
/// Whether to validate the `exp` field.
///
/// Defaults to `true`.
pub validate_exp: bool,
/// Whether to validate the `nbf` field.
///
/// Defaults to `false`.
pub validate_nbf: bool,
/// If it contains a value, the validation will check that the `aud` claim value is in the values provided.
///
/// Defaults to `None`.
pub aud: Option<Vec<String>>,
/// If it contains a value, the validation will check that the `iss` claim value is in the values provided.
///
/// Defaults to `None`.
pub iss: Option<Vec<String>>,
/// Whether to validate the JWT signature. Very insecure to turn that off!
///
/// Defaults to true.
pub validate_signature: bool,
}
impl Validation {
/// new Validation with default values
pub fn new() -> Self {
Default::default()
}
/// check that the `iss` claim is a member of the values provided
pub fn iss<T: ToString>(mut self, items: &[T]) -> Self {
self.iss = Some(items.iter().map(|x| x.to_string()).collect());
self
}
/// check that the `aud` claim is a member of the items provided
pub fn aud<T: ToString>(mut self, items: &[T]) -> Self {
self.aud = Some(items.iter().map(|x| x.to_string()).collect());
self
}
/// enables or disables exp validation
pub fn exp(mut self, val: bool) -> Self {
self.validate_exp = val;
self
}
/// enables or disables nbf validation
pub fn nbf(mut self, val: bool) -> Self {
self.validate_nbf = val;
self
}
/// Add some leeway (in seconds) to the `exp` and `nbf` validation to
/// account for clock skew.
pub fn leeway(mut self, value: u64) -> Self {
self.leeway = value;
self
}
/// Whether to validate the JWT cryptographic signature
/// Very insecure to turn that off, only do it if you know what you're doing.
pub fn disable_validation(mut self) -> Self {
self.validate_signature = false;
self
}
pub(crate) fn to_jwt_validation(&self, alg: Algorithm) -> jsonwebtoken::Validation {
let required_claims = if self.validate_exp {
let mut claims = HashSet::with_capacity(1);
claims.insert("exp".to_owned());
claims
} else {
HashSet::with_capacity(0)
};
let aud = self.aud.clone().map(|v| HashSet::from_iter(v.into_iter()));
let iss = self.iss.clone().map(|v| HashSet::from_iter(v.into_iter()));
let mut jwt_validation = jsonwebtoken::Validation::default();
jwt_validation.required_spec_claims = required_claims;
jwt_validation.leeway = self.leeway;
jwt_validation.validate_exp = self.validate_exp;
jwt_validation.validate_nbf = self.validate_nbf;
jwt_validation.iss = iss;
jwt_validation.aud = aud;
jwt_validation.sub = None;
jwt_validation.algorithms = vec![alg];
if !self.validate_signature {
jwt_validation.insecure_disable_signature_validation();
}
jwt_validation
}
}
impl Default for Validation {
fn default() -> Self {
Validation {
leeway: 60,
validate_exp: true,
validate_nbf: false,
iss: None,
aud: None,
validate_signature: true,
}
}
}

View file

@ -0,0 +1,47 @@
#![allow(dead_code)]
use lazy_static::lazy_static;
use serde_json::{json, Value};
lazy_static! {
pub static ref JWKS_RSA1: Value = json!({
"keys": [{
"kty": "RSA",
"n": "2pQeZdxa7q093K7bj5h6-leIpxfTnuAxzXdhjfGEJHxmt2ekHyCBWWWXCBiDn2RTcEBcy6gZqOW45Uy_tw-5e-Px1xFj1PykGEkRlOpYSAeWsNaAWvvpGB9m4zQ0PgZeMDDXE5IIBrY6YAzmGQxV-fcGGLhJnXl0-5_z7tKC7RvBoT3SGwlc_AmJqpFtTpEBn_fDnyqiZbpcjXYLExFpExm41xDitRKHWIwfc3dV8_vlNntlxCPGy_THkjdXJoHv2IJmlhvmr5_h03iGMLWDKSywxOol_4Wc1BT7Hb6byMxW40GKwSJJ4p7W8eI5mqggRHc8jlwSsTN9LZ2VOvO-XiVShZRVg7JeraGAfWwaIgIJ1D8C1h5Pi0iFpp2suxpHAXHfyLMJXuVotpXbDh4NDX-A4KRMgaxcfAcui_x6gybksq6gF90-9nfQfmVMVJctZ6M-FvRr-itd1Nef5WAtwUp1qyZygAXU3cH3rarscajmurOsP6dE1OHl3grY_eZhQxk33VBK9lavqNKPg6Q_PLiq1ojbYBj3bcYifJrsNeQwxldQP83aWt5rGtgZTehKVJwa40Uy_Grae1iRnsDtdSy5sTJIJ6EiShnWAdMoGejdiI8vpkjrdU8SWH8lv1KXI54DsbyAuke2cYz02zPWc6JEotQqI0HwhzU0KHyoY4s",
"e": "AQAB",
"kid": "rsa01",
"alg": "RS256",
"use": "sig"
}]
});
pub static ref JWKS_RSA2: Value = json!({
"keys": [{
"kty": "RSA",
"n": "yRE6rHuNR0QbHO3H3Kt2pOKGVhQqGZXInOduQNxXzuKlvQTLUTv4l4sggh5_CYYi_cvI-SXVT9kPWSKXxJXBXd_4LkvcPuUakBoAkfh-eiFVMh2VrUyWyj3MFl0HTVF9KwRXLAcwkREiS3npThHRyIxuy0ZMeZfxVL5arMhw1SRELB8HoGfG_AtH89BIE9jDBHZ9dLelK9a184zAf8LwoPLxvJb3Il5nncqPcSfKDDodMFBIMc4lQzDKL5gvmiXLXB1AGLm8KBjfE8s3L5xqi-yUod-j8MtvIj812dkS4QMiRVN_by2h3ZY8LYVGrqZXZTcgn2ujn8uKjXLZVD5TdQ",
"e": "AQAB",
"kid": "rsa02",
"alg": "RS256",
"use": "sig"
}]
});
pub static ref JWKS_EC1: Value = json!({
"keys": [{
"kty": "EC",
"crv": "P-256",
"x": "MZiwc5EVP_E3vkd2oKedr4lWVMN9vgdyBBpBIVFJjwY",
"y": "1npLU75B6M0mb01zUAVoeYJSDOlQJmvjBdqLPjJvy3Y",
"kid": "ec01",
"alg": "ES256",
"use": "sig"
}]
});
}
pub const JWT_RSA1_OK: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6InJzYTAxIn0.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjMwMDEiLCJzdWIiOiJiQGIuY29tIiwiYXVkIjoiYXVkMSIsImV4cCI6MjAwMDAwMDAwMCwibmJmIjoxNTE2MjM5MDIyfQ.d29YS5U-Cfur1DDxOeiYBxlEzRFQrVovuFdyIlrMpAfLLWudtpqiMxHKfTEM0ohHrk4ahf7nWhMamuiQUOEZSYpx7ze-4f47FGU4RxFLVSZw7VUWFrNYKkmRlgFsCbpdRLb5in6YDIaqrUnr2tqF9c3vUo_15lLgNn__xDG9_49A8UvbNFGvDm_z53aYGBPdgWVmwrRU5lmHH0tYcLMyiqQKfnM4jr__klIeVGBpJ2V_2qZyHvvevEtiiV7EGWZxaA-cYzeaO-_24nVBvPYrcdib84pz-a4JWhmnUobfzvtbdKEy12abxB2TvpzikBbX5etiDx92cPP_9Kf_51BncmwC_anRIwCSEe5TEgduihYS9yucOGgjP09sjlPPvdGAE6vcl35eR2fizJo7KU6Ol8DoUSDMhuPS-KQ_bFpCDK1iPtsXw514WZQZL1qXF61yd5QZ3wvckR8s_pV0XcFHWg_TpupNC3Yn6zlYU9l8NLkWiIudJVAM2pe-MSu292FyR2ytLISrNqHtk-e4_MIoviqyswvmtHZivoFWkq_CE2V9RyLX4WXaVEJLf4FihfCMFGZVfON2B8N2PfoPMuAlE1otQerbKwwR_TYjOFJRG1HdIDqvNDQ-LeJDKKX0NzCHwoJIqC-X7m6F1QIcaupOWnXyoSndvsi6g1jAlP_fTCI";
pub const JWT_RSA2_OK: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6InJzYTAyIn0.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjMwMDEiLCJzdWIiOiJiQGIuY29tIiwiZXhwIjoyMDAwMDAwMDAwLCJuYmYiOjE1MTYyMzkwMjJ9.tWyA4ve2CY6GruBch_qIf8f1PgCEhqmrZ1J5XBuwO_v-P-PSLe3MWpkPAMdIDE5QE19ItUcGdJblhiyPb0tJJtrDHVYER7q8X4fOjQjY_NlFK6Bd1GtZS2DCA5EPxIX8l7Jpn8fPvbyamagLwnB_waQaYBteTGnOkLmz3F3sqC8KdO9lyu5v7BknC1f56ZOvr_DiInkTiAsTWqX4nS2KYRjcz4HcxcPO7O0CFXqcOTF_e3ntmq4rQV9LHCaEnuXj2WZtnX423CMkcG0uYzsnmWAMPB6IlDKejPnAJThMjjuJhze1gGbP1U8c53UbEhfHEZgJ2N634YEXMfsojZ5VzQ";
pub const JWT_EC1_OK: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6ImVjMDEifQ.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjMwMDEiLCJzdWIiOiJiQGIuY29tIiwiYXVkIjoiYXVkMSIsImV4cCI6MjAwMDAwMDAwMCwibmJmIjoxNTE2MjM5MDIyfQ.orTVTRdQnktCg1Ar_mo9IvN__5-s-q5oCZaUUWaL2I8HAkIq68GV6ACqvhrxQMB-OInX0hY9pBWGYbrFJjCwKA";
pub const JWT_EC2_OK: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6ImVjMDIifQ.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjMwMDEiLCJzdWIiOiJiQGIuY29tIiwiYXVkIjoiYXVkMSIsImV4cCI6MjAwMDAwMDAwMCwibmJmIjoxNTE2MjM5MDIyfQ.QnsVM9CA11VpwDr6e_aHzxlLSXTQ7yVH5oxTR1yIWBPKnosjk1EIIBMcSjD81fZCrON2kX4TNkfSCxSCL8GI3g";
pub const JWT_EC1_EXP_KO: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6ImVjMDEifQ.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjMwMDEiLCJzdWIiOiJib2IiLCJleHAiOjE1MTYyMzkwMjIsIm5iZiI6MTUxNjIzOTAyMn0.MNmY66S3NgSAbWwZP0hfC5pme3SM7B3yvFhBFLQH-cU3enP0G8bBzDOhpjmli9uKQitkIQxffwu2Au9wTUraTQ";
pub const JWT_EC1_NBF_KO: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6ImVjMDEifQ.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjMwMDEiLCJzdWIiOiJib2IiLCJleHAiOjIwMDAwMDAwMDAsIm5iZiI6MjAwMDAwMDAwMH0.d5MRfwcToMxR7O7NEt3qUj-MUKKpG9BZW1w6ihyfN95ZULoMajr7mtYY2R2LS96oBYgp3OdlR4tkHmdqDpvCSA";
pub const JWT_ED1_OK: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImVkMDEifQ.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjMwMDEiLCJzdWIiOiJiQGIuY29tIiwiYXVkIjoiYXVkMSIsImV4cCI6MjAwMDAwMDAwMCwibmJmIjoxNTE2MjM5MDIyfQ.U2eaP1EzRiLDRRPJTVjOMy4y40uAiW8MeryWJAjU-QPxU_PnuzatvrRjntTcdW7hXx0EWIezecJuXzp2UrBqAw";
pub const JWT_ED2_OK: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImVkMDIifQ.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjMwMDEiLCJzdWIiOiJiQGIuY29tIiwiYXVkIjoiYXVkMSIsImV4cCI6MjAwMDAwMDAwMCwibmJmIjoxNTE2MjM5MDIyfQ.xFrGhImKI1irksznuU9DoLk24bbdHhurbVRoUdZSb_FNlav1Jw49eMyKfeJUPy8IdMCtnG33K9xHuCRjm5IcAA";

View file

@ -14,52 +14,15 @@ use hyper::Body;
use jwt_authorizer::{JwtAuthorizer, JwtClaims, Refresh, RefreshStrategy}; use jwt_authorizer::{JwtAuthorizer, JwtClaims, Refresh, RefreshStrategy};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::{json, Value}; use serde_json::Value;
use tower::Service; use tower::Service;
use tower::ServiceExt; use tower::ServiceExt;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
lazy_static! { use crate::common::{JWT_RSA1_OK, JWT_RSA2_OK};
static ref JWKS_RSA1: Value = json!({
"keys": [{
"kty": "RSA",
"n": "2pQeZdxa7q093K7bj5h6-leIpxfTnuAxzXdhjfGEJHxmt2ekHyCBWWWXCBiDn2RTcEBcy6gZqOW45Uy_tw-5e-Px1xFj1PykGEkRlOpYSAeWsNaAWvvpGB9m4zQ0PgZeMDDXE5IIBrY6YAzmGQxV-fcGGLhJnXl0-5_z7tKC7RvBoT3SGwlc_AmJqpFtTpEBn_fDnyqiZbpcjXYLExFpExm41xDitRKHWIwfc3dV8_vlNntlxCPGy_THkjdXJoHv2IJmlhvmr5_h03iGMLWDKSywxOol_4Wc1BT7Hb6byMxW40GKwSJJ4p7W8eI5mqggRHc8jlwSsTN9LZ2VOvO-XiVShZRVg7JeraGAfWwaIgIJ1D8C1h5Pi0iFpp2suxpHAXHfyLMJXuVotpXbDh4NDX-A4KRMgaxcfAcui_x6gybksq6gF90-9nfQfmVMVJctZ6M-FvRr-itd1Nef5WAtwUp1qyZygAXU3cH3rarscajmurOsP6dE1OHl3grY_eZhQxk33VBK9lavqNKPg6Q_PLiq1ojbYBj3bcYifJrsNeQwxldQP83aWt5rGtgZTehKVJwa40Uy_Grae1iRnsDtdSy5sTJIJ6EiShnWAdMoGejdiI8vpkjrdU8SWH8lv1KXI54DsbyAuke2cYz02zPWc6JEotQqI0HwhzU0KHyoY4s",
"e": "AQAB",
"kid": "rsa01",
"alg": "RS256",
"use": "sig"
}]
});
static ref JWKS_RSA2: Value = json!({
"keys": [{
"kty": "RSA",
"n": "yRE6rHuNR0QbHO3H3Kt2pOKGVhQqGZXInOduQNxXzuKlvQTLUTv4l4sggh5_CYYi_cvI-SXVT9kPWSKXxJXBXd_4LkvcPuUakBoAkfh-eiFVMh2VrUyWyj3MFl0HTVF9KwRXLAcwkREiS3npThHRyIxuy0ZMeZfxVL5arMhw1SRELB8HoGfG_AtH89BIE9jDBHZ9dLelK9a184zAf8LwoPLxvJb3Il5nncqPcSfKDDodMFBIMc4lQzDKL5gvmiXLXB1AGLm8KBjfE8s3L5xqi-yUod-j8MtvIj812dkS4QMiRVN_by2h3ZY8LYVGrqZXZTcgn2ujn8uKjXLZVD5TdQ",
"e": "AQAB",
"kid": "rsa02",
"alg": "RS256",
"use": "sig"
}]
});
static ref JWKS_EC1: Value = json!({
"keys": [{
"kty": "EC",
"crv": "P-256",
"x": "MZiwc5EVP_E3vkd2oKedr4lWVMN9vgdyBBpBIVFJjwY",
"y": "1npLU75B6M0mb01zUAVoeYJSDOlQJmvjBdqLPjJvy3Y",
"kid": "ec01",
"alg": "ES256",
"use": "sig"
}]
});
}
const JWT_RSA1_OK: &str = "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6InJzYTAxIn0.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjMwMDEiLCJzdWIiOiJiQGIuY29tIiwiZXhwIjoyMDAwMDAwMDAwLCJuYmYiOjE1MTYyMzkwMjJ9.pmm8Kdk-SvycXIGpWb1R0DuP5nlB7w4QQS7trhN_OjOpbk0A8F_lC4BdClz3rol2Pgo61lcFckJgjNBj34DQGeTGOtvxdiUXNgi1aKiXH4AyPzZeZx30PgFxa1fxhuZhBAj6xIZKBSBQvVyjeVQzAScINRCBX8zfCaXSU1ZCUkJl5vbD7zT-cYIFU76we9HcIYKRXwTiAyoNn3Lixa1H3_t5sbx3om2WlIB2x-sGpoDFDjorcuJT1yQx3grTRTBzHyRBRjZ3e8wrMbiacy-m3WoEFdkssQgYi_dSQH0hvxgacvGWayK0UqD7O5UL6EzTA2feXbgA_68o5gfvSnM8CUsPut5gZr-gwVbQKPbBdCQtl_wXIMot7UNKYEiFV38x5EmUr-ShzQcditW6fciguuY1Qav502UE1UMXvt5p8-kYxw2AaaVd6iTgQBzkBrtvywMYWzIwzGNA70RvUhI2rlgcn8GEU_51Tv_NMHjp6CjDbAxQVKa0PlcRE4pd6yk_IJSR4Nska_8BQZdPbsFn--z_XHEDoRZQ1C1M6m77xVndg3zX0sNQPXfWsttCbBmaHvMKTOp0cH9rlWB9r9nTo9fn8jcfqlak2O2IAzfzsOdVfUrES6T1UWkWobs9usGgqJuIkZHbDd4tmXyPRT4wrU7hxEyE9cuvuZPAi8GYt80"; mod common;
const JWT_RSA2_OK: &str = "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6InJzYTAyIn0.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjMwMDEiLCJzdWIiOiJiQGIuY29tIiwiZXhwIjoyMDAwMDAwMDAwLCJuYmYiOjE1MTYyMzkwMjJ9.tWyA4ve2CY6GruBch_qIf8f1PgCEhqmrZ1J5XBuwO_v-P-PSLe3MWpkPAMdIDE5QE19ItUcGdJblhiyPb0tJJtrDHVYER7q8X4fOjQjY_NlFK6Bd1GtZS2DCA5EPxIX8l7Jpn8fPvbyamagLwnB_waQaYBteTGnOkLmz3F3sqC8KdO9lyu5v7BknC1f56ZOvr_DiInkTiAsTWqX4nS2KYRjcz4HcxcPO7O0CFXqcOTF_e3ntmq4rQV9LHCaEnuXj2WZtnX423CMkcG0uYzsnmWAMPB6IlDKejPnAJThMjjuJhze1gGbP1U8c53UbEhfHEZgJ2N634YEXMfsojZ5VzQ";
// const JWT_EC1_OK: &str = "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6ImVjMDEifQ.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjMwMDEiLCJzdWIiOiJiQGIuY29tIiwiZXhwIjoyMDAwMDAwMDAwLCJuYmYiOjE1MTYyMzkwMjJ9.AsAX8XQdsQMI7NGNJOPE8LFFaKJ_nYXeKBwl2NZACbPhCiRj7FgxIw0UVcpmRVzK0BNbb9S4lFocaTLo9DsCeQ";
// const JWT_EC2_OK: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6ImVjMDIifQ.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjMwMDEiLCJzdWIiOiJiQGIuY29tIiwiZXhwIjoyMDAwMDAwMDAwLCJuYmYiOjE1MTYyMzkwMjJ9.DJFNPyfuL5-ifcAxRCvneo7SdtDu0cfJyYmv2Gl4rmJOjKlzDx3GDamYa0cGLy8zcYYdpDMJ-s1WKzlGC_Hiyw";
// const JWT_ED1_OK: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImVkMDEifQ.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjMwMDEiLCJzdWIiOiJiQGIuY29tIiwiZXhwIjoyMDAwMDAwMDAwLCJuYmYiOjE1MTYyMzkwMjJ9.5bFOZqc-lBFy4gFifQ_CTx1A3R6Nry71gdi7KH2GGvTZQC_ZI1vNbqGnWQhpR6n_jUd9ICUc0pPI5iLCB6K1Bg";
// const JWT_ED2_OK: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImVkMDIifQ.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjMwMDEiLCJzdWIiOiJiQGIuY29tIiwiZXhwIjoyMDAwMDAwMDAwLCJuYmYiOjE1MTYyMzkwMjJ9.Yfe88E26UEJ8x13h8xv2XtBrQ7O5E5UtS9t6-hRbo_pMSxKui13X0uNleRPHaZFfzK4AO033m8gHYHxQDLkTCg";
/// Static variable to ensure that logging is only initialized once. /// Static variable to ensure that logging is only initialized once.
pub static INITIALIZED: Once = Once::new(); pub static INITIALIZED: Once = Once::new();
@ -99,7 +62,7 @@ fn discovery(uri: &str) -> Json<Value> {
async fn jwks() -> Json<Value> { async fn jwks() -> Json<Value> {
Arc::clone(&JWKS_COUNTER).fetch_add(1, Ordering::Relaxed); Arc::clone(&JWKS_COUNTER).fetch_add(1, Ordering::Relaxed);
Json(JWKS_RSA1.clone()) Json(common::JWKS_RSA1.clone())
} }
fn run_jwks_server() -> String { fn run_jwks_server() -> String {
@ -166,7 +129,7 @@ async fn make_proteced_request(app: &mut Router, bearer: &str) -> Response {
.call( .call(
Request::builder() Request::builder()
.uri("/protected") .uri("/protected")
.header("Authorization", bearer) .header("Authorization", format!("Bearer {bearer}"))
.body(Body::empty()) .body(Body::empty())
.unwrap(), .unwrap(),
) )

View file

@ -1,3 +1,5 @@
mod common;
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use axum::{ use axum::{
@ -8,11 +10,11 @@ mod tests {
Router, Router,
}; };
use http::{header, HeaderValue}; use http::{header, HeaderValue};
use jwt_authorizer::{JwtAuthorizer, JwtClaims}; use jwt_authorizer::{validation::Validation, JwtAuthorizer, JwtClaims};
use serde::Deserialize; use serde::Deserialize;
use tower::ServiceExt; use tower::ServiceExt;
const JWT_RSA_OK: &str = "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImtleS1yc2EifQ.eyJzdWIiOiJiQGIuY29tIiwiZXhwIjoyMDAwMDAwMDAwfQ.K9QFjvVquRF2-Wt1QRfipOGwiYsmRs7SAwqKskHemFb9BRRZutpfV4oEoHaXMLomTUe8rH0TMjpKcweYK_H1I8D4r-mAN216oUfxCQiFWDB8T2VBI8um-efUg67i2myDZJr5VXdZH8ywj7bn9LyNS4I_xT-J3XvsngeCpuxVSRiYu4FkcUkLrPzbu2sDyBXFqYO9FOorZ8sl0Ninc93fWT2uUrEG8jRyWCa4xpoqbKbm7CN7T2tOKF7mx_xdSPTeSM-U9mUiHsMOrXi1S05IM0hvNJrBduLS6sMTFWrVhis6zqnuxDOirwZS-aN0_SgMDnZTFPsCh8dkqFde1Pv1IYjZfr5OOHjQ9QWj6UDjam6M1eWVPK6QLlxv5bU_gnlAiHm9wJX38-REwmVhIJIBzKxsgJAu1gnRBxe36OM3rkgYxpB86YvfDyOoFlqx8erdxYv38AtvJibe4HB6KLndp_QMm5XXQsbfyEXWGe8hzDwozdhGeQsJXz7PcI3KPlv19PrUM8njElFpOiyfAEXwbtp1EZTzMZ4ZNF6LLFy1fpLcosgyp05o_2YMvngltSnN3v0IPncJx50StdYsoxPN9Ac_nH8VbNlHfmPHMklD1plof0pYf5SiL8yCQP9Uiw9NrN2PeQzbveMKF1T1UNbn2tefxoxr3k6sgWiMH_g_kkk"; use crate::common;
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
struct User { struct User {
@ -33,7 +35,7 @@ mod tests {
.oneshot( .oneshot(
Request::builder() Request::builder()
.uri("/protected") .uri("/protected")
.header("Authorization", bearer) .header("Authorization", format!("Bearer {bearer}"))
.body(Body::empty()) .body(Body::empty())
.unwrap(), .unwrap(),
) )
@ -63,10 +65,24 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn protected_with_jwt() { async fn protected_with_jwt() {
let response = make_proteced_request(JwtAuthorizer::from_rsa_pem("../config/rsa-public1.pem"), JWT_RSA_OK).await; let response = make_proteced_request(
JwtAuthorizer::from_ed_pem("../config/ed25519-public2.pem"),
common::JWT_ED2_OK,
)
.await;
assert_eq!(response.status(), StatusCode::OK); assert_eq!(response.status(), StatusCode::OK);
let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
assert_eq!(&body[..], b"hello: b@b.com");
let response =
make_proteced_request(JwtAuthorizer::from_ec_pem("../config/ecdsa-public2.pem"), common::JWT_EC2_OK).await;
assert_eq!(response.status(), StatusCode::OK);
let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
assert_eq!(&body[..], b"hello: b@b.com");
let response =
make_proteced_request(JwtAuthorizer::from_rsa_pem("../config/rsa-public2.pem"), common::JWT_RSA2_OK).await;
assert_eq!(response.status(), StatusCode::OK);
let body = hyper::body::to_bytes(response.into_body()).await.unwrap(); let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
assert_eq!(&body[..], b"hello: b@b.com"); assert_eq!(&body[..], b"hello: b@b.com");
} }
@ -82,16 +98,16 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn protected_with_claims_check() { async fn protected_with_claims_check() {
let rsp_ok = make_proteced_request( let rsp_ok = make_proteced_request(
JwtAuthorizer::from_rsa_pem("../config/rsa-public1.pem").check(|_| true), JwtAuthorizer::from_rsa_pem("../config/rsa-public2.pem").check(|_| true),
JWT_RSA_OK, common::JWT_RSA2_OK,
) )
.await; .await;
assert_eq!(rsp_ok.status(), StatusCode::OK); assert_eq!(rsp_ok.status(), StatusCode::OK);
let rsp_ko = make_proteced_request( let rsp_ko = make_proteced_request(
JwtAuthorizer::from_rsa_pem("../config/rsa-public1.pem").check(|_| false), JwtAuthorizer::from_rsa_pem("../config/rsa-public2.pem").check(|_| false),
JWT_RSA_OK, common::JWT_RSA2_OK,
) )
.await; .await;
@ -110,7 +126,8 @@ mod tests {
// but should be 500 when checking. // but should be 500 when checking.
#[tokio::test] #[tokio::test]
async fn protected_with_bad_jwks_url() { async fn protected_with_bad_jwks_url() {
let response = make_proteced_request(JwtAuthorizer::from_jwks_url("http://bad-url/xxx/yyy"), JWT_RSA_OK).await; let response =
make_proteced_request(JwtAuthorizer::from_jwks_url("http://bad-url/xxx/yyy"), common::JWT_RSA1_OK).await;
assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR); assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
} }
@ -128,4 +145,140 @@ mod tests {
assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR); assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
} }
// --------------------
// VALIDATION
// ---------------------
#[tokio::test]
async fn validate_signature() {
let response = make_proteced_request(
JwtAuthorizer::from_rsa_pem("../config/rsa-public1.pem").validation(Validation::new().disable_validation()),
common::JWT_EC2_OK,
)
.await;
assert_eq!(response.status(), StatusCode::OK);
let response = make_proteced_request(
JwtAuthorizer::from_rsa_pem("../config/rsa-public1.pem").validation(Validation::new()),
common::JWT_EC2_OK,
)
.await;
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn validate_iss() {
let response = make_proteced_request(
JwtAuthorizer::from_ec_pem("../config/ecdsa-public1.pem").validation(Validation::new().iss(&["bad-iss"])),
common::JWT_EC1_OK,
)
.await;
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
let response = make_proteced_request(
JwtAuthorizer::from_ec_pem("../config/ecdsa-public1.pem").validation(Validation::new()),
common::JWT_EC1_OK,
)
.await;
assert_eq!(response.status(), StatusCode::OK);
let response = make_proteced_request(
JwtAuthorizer::from_ec_pem("../config/ecdsa-public1.pem")
.validation(Validation::new().iss(&["http://localhost:3001"])),
common::JWT_EC1_OK,
)
.await;
assert_eq!(response.status(), StatusCode::OK);
}
#[tokio::test]
async fn validate_aud() {
let response = make_proteced_request(
JwtAuthorizer::from_ed_pem("../config/ed25519-public1.pem").validation(Validation::new().aud(&["bad-aud"])),
common::JWT_ED1_OK,
)
.await;
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
let response = make_proteced_request(
JwtAuthorizer::from_ed_pem("../config/ed25519-public1.pem").validation(Validation::new()),
common::JWT_ED1_OK,
)
.await;
assert_eq!(response.status(), StatusCode::OK);
let response = make_proteced_request(
JwtAuthorizer::from_ed_pem("../config/ed25519-public1.pem").validation(Validation::new().aud(&["aud1"])),
common::JWT_ED1_OK,
)
.await;
assert_eq!(response.status(), StatusCode::OK);
}
#[tokio::test]
async fn validate_exp() {
// DEFAULT -> ENABLED
let response = make_proteced_request(
JwtAuthorizer::from_ec_pem("../config/ecdsa-public1.pem").validation(Validation::new()),
common::JWT_EC1_EXP_KO,
)
.await;
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
// DISABLED
let response = make_proteced_request(
JwtAuthorizer::from_ec_pem("../config/ecdsa-public1.pem").validation(Validation::new().exp(false)),
common::JWT_EC1_EXP_KO,
)
.await;
assert_eq!(response.status(), StatusCode::OK);
// ENABLED
let response = make_proteced_request(
JwtAuthorizer::from_ec_pem("../config/ecdsa-public1.pem").validation(Validation::new().exp(true)),
common::JWT_EC1_EXP_KO,
)
.await;
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
let response = make_proteced_request(
JwtAuthorizer::from_ec_pem("../config/ecdsa-public1.pem").validation(Validation::new().exp(true)),
common::JWT_EC1_OK,
)
.await;
assert_eq!(response.status(), StatusCode::OK);
}
#[tokio::test]
async fn validate_nbf() {
// DEFAULT -> DISABLED
let response = make_proteced_request(
JwtAuthorizer::from_ec_pem("../config/ecdsa-public1.pem").validation(Validation::new()),
common::JWT_EC1_NBF_KO,
)
.await;
assert_eq!(response.status(), StatusCode::OK);
// DISABLED
let response = make_proteced_request(
JwtAuthorizer::from_ec_pem("../config/ecdsa-public1.pem").validation(Validation::new().nbf(false)),
common::JWT_EC1_NBF_KO,
)
.await;
assert_eq!(response.status(), StatusCode::OK);
// ENABLED
let response = make_proteced_request(
JwtAuthorizer::from_ec_pem("../config/ecdsa-public1.pem").validation(Validation::new().nbf(true)),
common::JWT_EC1_NBF_KO,
)
.await;
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
let response = make_proteced_request(
JwtAuthorizer::from_ec_pem("../config/ecdsa-public1.pem").validation(Validation::new().nbf(true)),
common::JWT_EC1_OK,
)
.await;
assert_eq!(response.status(), StatusCode::OK);
}
} }