mirror of
https://github.com/TECHNOFAB11/jwt-authorizer.git
synced 2025-12-11 23:50:07 +01:00
fix(jwt source): cookie from request (#18)
- JwtSource:Bearer renamed to AuthorizationHeader for more consistency with jwt terminology - documentation added - the token cookie should be taken from request not from the tower-cookies middleware jar - dependency on tower-cookies is no longer needed - tests added
This commit is contained in:
parent
3292d59d1c
commit
bad5ad18f3
5 changed files with 74 additions and 58 deletions
29
Cargo.lock
generated
29
Cargo.lock
generated
|
|
@ -178,17 +178,6 @@ dependencies = [
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cookie"
|
|
||||||
version = "0.17.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24"
|
|
||||||
dependencies = [
|
|
||||||
"percent-encoding",
|
|
||||||
"time",
|
|
||||||
"version_check",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.9.3"
|
version = "0.9.3"
|
||||||
|
|
@ -751,7 +740,6 @@ dependencies = [
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower",
|
"tower",
|
||||||
"tower-cookies",
|
|
||||||
"tower-http",
|
"tower-http",
|
||||||
"tower-layer",
|
"tower-layer",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
|
|
@ -1572,23 +1560,6 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tower-cookies"
|
|
||||||
version = "0.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "40f38d941a2ffd8402b36e02ae407637a9caceb693aaf2edc910437db0f36984"
|
|
||||||
dependencies = [
|
|
||||||
"async-trait",
|
|
||||||
"axum-core",
|
|
||||||
"cookie",
|
|
||||||
"futures-util",
|
|
||||||
"http",
|
|
||||||
"parking_lot",
|
|
||||||
"pin-project-lite",
|
|
||||||
"tower-layer",
|
|
||||||
"tower-service",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower-http"
|
name = "tower-http"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@ tokio = { version = "1.25", features = ["full"] }
|
||||||
tower-http = { version = "0.4", features = ["trace", "auth"] }
|
tower-http = { version = "0.4", features = ["trace", "auth"] }
|
||||||
tower-layer = "0.3"
|
tower-layer = "0.3"
|
||||||
tower-service = "0.3"
|
tower-service = "0.3"
|
||||||
tower-cookies = "0.9.0"
|
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ use std::future::Future;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
use tower_cookies::Cookies;
|
|
||||||
use tower_layer::Layer;
|
use tower_layer::Layer;
|
||||||
use tower_service::Service;
|
use tower_service::Service;
|
||||||
|
|
||||||
|
|
@ -33,7 +32,7 @@ where
|
||||||
refresh: Option<Refresh>,
|
refresh: Option<Refresh>,
|
||||||
claims_checker: Option<FnClaimsChecker<C>>,
|
claims_checker: Option<FnClaimsChecker<C>>,
|
||||||
validation: Option<Validation>,
|
validation: Option<Validation>,
|
||||||
pub jwt_source: JwtSource,
|
jwt_source: JwtSource,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// authorization layer builder
|
/// authorization layer builder
|
||||||
|
|
@ -48,7 +47,7 @@ where
|
||||||
refresh: Default::default(),
|
refresh: Default::default(),
|
||||||
claims_checker: None,
|
claims_checker: None,
|
||||||
validation: None,
|
validation: None,
|
||||||
jwt_source: JwtSource::Bearer,
|
jwt_source: JwtSource::AuthorizationHeader,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,7 +58,7 @@ where
|
||||||
refresh: Default::default(),
|
refresh: Default::default(),
|
||||||
claims_checker: None,
|
claims_checker: None,
|
||||||
validation: None,
|
validation: None,
|
||||||
jwt_source: JwtSource::Bearer,
|
jwt_source: JwtSource::AuthorizationHeader,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,7 +69,7 @@ where
|
||||||
refresh: Default::default(),
|
refresh: Default::default(),
|
||||||
claims_checker: None,
|
claims_checker: None,
|
||||||
validation: None,
|
validation: None,
|
||||||
jwt_source: JwtSource::Bearer,
|
jwt_source: JwtSource::AuthorizationHeader,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,7 +80,7 @@ where
|
||||||
refresh: Default::default(),
|
refresh: Default::default(),
|
||||||
claims_checker: None,
|
claims_checker: None,
|
||||||
validation: None,
|
validation: None,
|
||||||
jwt_source: JwtSource::Bearer,
|
jwt_source: JwtSource::AuthorizationHeader,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -92,7 +91,7 @@ where
|
||||||
refresh: Default::default(),
|
refresh: Default::default(),
|
||||||
claims_checker: None,
|
claims_checker: None,
|
||||||
validation: None,
|
validation: None,
|
||||||
jwt_source: JwtSource::Bearer,
|
jwt_source: JwtSource::AuthorizationHeader,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -103,7 +102,7 @@ where
|
||||||
refresh: Default::default(),
|
refresh: Default::default(),
|
||||||
claims_checker: None,
|
claims_checker: None,
|
||||||
validation: None,
|
validation: None,
|
||||||
jwt_source: JwtSource::Bearer,
|
jwt_source: JwtSource::AuthorizationHeader,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -114,7 +113,7 @@ where
|
||||||
refresh: Default::default(),
|
refresh: Default::default(),
|
||||||
claims_checker: None,
|
claims_checker: None,
|
||||||
validation: None,
|
validation: None,
|
||||||
jwt_source: JwtSource::Bearer,
|
jwt_source: JwtSource::AuthorizationHeader,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,7 +124,7 @@ where
|
||||||
refresh: Default::default(),
|
refresh: Default::default(),
|
||||||
claims_checker: None,
|
claims_checker: None,
|
||||||
validation: None,
|
validation: None,
|
||||||
jwt_source: JwtSource::Bearer,
|
jwt_source: JwtSource::AuthorizationHeader,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -136,7 +135,7 @@ where
|
||||||
refresh: Default::default(),
|
refresh: Default::default(),
|
||||||
claims_checker: None,
|
claims_checker: None,
|
||||||
validation: None,
|
validation: None,
|
||||||
jwt_source: JwtSource::Bearer,
|
jwt_source: JwtSource::AuthorizationHeader,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -175,6 +174,9 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// configures the source of the bearer token
|
||||||
|
///
|
||||||
|
/// (default: AuthorizationHeader)
|
||||||
pub fn jwt_source(mut self, src: JwtSource) -> JwtAuthorizer<C> {
|
pub fn jwt_source(mut self, src: JwtSource) -> JwtAuthorizer<C> {
|
||||||
self.jwt_source = src;
|
self.jwt_source = src;
|
||||||
|
|
||||||
|
|
@ -214,20 +216,13 @@ where
|
||||||
let h = request.headers();
|
let h = request.headers();
|
||||||
|
|
||||||
let token = match &self.jwt_source {
|
let token = match &self.jwt_source {
|
||||||
layer::JwtSource::Bearer => {
|
layer::JwtSource::AuthorizationHeader => {
|
||||||
let bearer_o: Option<Authorization<Bearer>> = h.typed_get();
|
let bearer_o: Option<Authorization<Bearer>> = h.typed_get();
|
||||||
bearer_o.map(|b| String::from(b.0.token()))
|
bearer_o.map(|b| String::from(b.0.token()))
|
||||||
}
|
}
|
||||||
layer::JwtSource::Cookie(name) => {
|
layer::JwtSource::Cookie(name) => h
|
||||||
if let Some(c) = request.extensions().get::<Cookies>() {
|
.typed_get::<headers::Cookie>()
|
||||||
c.get(name.as_str()).map(|c| String::from(c.value()))
|
.and_then(|c| c.get(name.as_str()).map(String::from)),
|
||||||
} else {
|
|
||||||
tracing::warn!(
|
|
||||||
"You have to add the tower_cookies::CookieManagerLayer middleware to use Cookies as JWT source."
|
|
||||||
);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
if let Some(token) = token {
|
if let Some(token) = token {
|
||||||
|
|
@ -281,10 +276,19 @@ where
|
||||||
|
|
||||||
// ---------- AsyncAuthorizationService --------
|
// ---------- AsyncAuthorizationService --------
|
||||||
|
|
||||||
|
/// Source of the bearer token
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum JwtSource {
|
pub enum JwtSource {
|
||||||
Bearer,
|
/// Storing the bearer token in Authorization header
|
||||||
|
///
|
||||||
|
/// (default)
|
||||||
|
AuthorizationHeader,
|
||||||
|
/// Cookies
|
||||||
|
///
|
||||||
|
/// (be careful when using cookies, some precautions must be taken, cf. RFC6750)
|
||||||
Cookie(String),
|
Cookie(String),
|
||||||
|
// TODO: "Form-Encoded Content Parameter" may be added in the future (OAuth 2.1 / 5.2.1.2)
|
||||||
|
// FormParam,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use axum::{response::Response, routing::get, Json, Router};
|
use axum::{response::Response, routing::get, Json, Router};
|
||||||
use http::{Request, StatusCode};
|
use http::{header::AUTHORIZATION, Request, StatusCode};
|
||||||
use hyper::Body;
|
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;
|
||||||
|
|
@ -129,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", format!("Bearer {bearer}"))
|
.header(AUTHORIZATION.as_str(), format!("Bearer {bearer}"))
|
||||||
.body(Body::empty())
|
.body(Body::empty())
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ mod tests {
|
||||||
Router,
|
Router,
|
||||||
};
|
};
|
||||||
use http::{header, HeaderValue};
|
use http::{header, HeaderValue};
|
||||||
use jwt_authorizer::{validation::Validation, JwtAuthorizer, JwtClaims};
|
use jwt_authorizer::{layer::JwtSource, validation::Validation, JwtAuthorizer, JwtClaims};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tower::ServiceExt;
|
use tower::ServiceExt;
|
||||||
|
|
||||||
|
|
@ -29,13 +29,13 @@ mod tests {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn make_proteced_request(jwt_auth: JwtAuthorizer<User>, bearer: &str) -> Response {
|
async fn proteced_request_with_header(jwt_auth: JwtAuthorizer<User>, header_name: &str, header_value: &str) -> Response {
|
||||||
app(jwt_auth)
|
app(jwt_auth)
|
||||||
.await
|
.await
|
||||||
.oneshot(
|
.oneshot(
|
||||||
Request::builder()
|
Request::builder()
|
||||||
.uri("/protected")
|
.uri("/protected")
|
||||||
.header("Authorization", format!("Bearer {bearer}"))
|
.header(header_name, header_value)
|
||||||
.body(Body::empty())
|
.body(Body::empty())
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
)
|
||||||
|
|
@ -43,6 +43,10 @@ mod tests {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn make_proteced_request(jwt_auth: JwtAuthorizer<User>, bearer: &str) -> Response {
|
||||||
|
proteced_request_with_header(jwt_auth, "Authorization", &format!("Bearer {bearer}")).await
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn protected_without_jwt() {
|
async fn protected_without_jwt() {
|
||||||
let jwt_auth: JwtAuthorizer<User> = JwtAuthorizer::from_rsa_pem("../config/rsa-public1.pem");
|
let jwt_auth: JwtAuthorizer<User> = JwtAuthorizer::from_rsa_pem("../config/rsa-public1.pem");
|
||||||
|
|
@ -281,4 +285,42 @@ mod tests {
|
||||||
.await;
|
.await;
|
||||||
assert_eq!(response.status(), StatusCode::OK);
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --------------------
|
||||||
|
// jwt_source
|
||||||
|
// ---------------------
|
||||||
|
#[tokio::test]
|
||||||
|
async fn jwt_source_cookie() {
|
||||||
|
// OK
|
||||||
|
let response = proteced_request_with_header(
|
||||||
|
JwtAuthorizer::from_rsa_pem("../config/rsa-public1.pem").jwt_source(JwtSource::Cookie("ccc".to_owned())),
|
||||||
|
header::COOKIE.as_str(),
|
||||||
|
&format!("ccc={}", common::JWT_RSA1_OK),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
|
|
||||||
|
// Cookie missing
|
||||||
|
let response = proteced_request_with_header(
|
||||||
|
JwtAuthorizer::from_rsa_pem("../config/rsa-public1.pem").jwt_source(JwtSource::Cookie("ccc".to_owned())),
|
||||||
|
header::COOKIE.as_str(),
|
||||||
|
&format!("bad_cookie={}", common::JWT_EC2_OK),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
|
||||||
|
assert_eq!(response.headers().get(header::WWW_AUTHENTICATE).unwrap(), &"Bearer");
|
||||||
|
|
||||||
|
// Invalid Token
|
||||||
|
let response = proteced_request_with_header(
|
||||||
|
JwtAuthorizer::from_rsa_pem("../config/rsa-public1.pem").jwt_source(JwtSource::Cookie("ccc".to_owned())),
|
||||||
|
header::COOKIE.as_str(),
|
||||||
|
&format!("ccc={}", common::JWT_EC2_OK),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
|
||||||
|
assert_eq!(
|
||||||
|
response.headers().get(header::WWW_AUTHENTICATE).unwrap(),
|
||||||
|
&"Bearer error=\"invalid_token\""
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue