feat: implementation of named cookie as jwt source (#10)

* feat: working but naive implementation of named cookie as jwt source

* refactor: add with_jwt_source to JwtAuthorizer. Make Bearer default

* fix: fix the demo-server. Remove JWTSource

* refactor: rename with_jwt_source() -> jwt_source()
This commit is contained in:
Felix B. Bause 2023-03-30 07:25:39 +02:00 committed by GitHub
parent 783ed7e340
commit 9054f400dc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 76 additions and 11 deletions

29
Cargo.lock generated
View file

@ -178,6 +178,17 @@ 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"
@ -740,6 +751,7 @@ dependencies = [
"thiserror", "thiserror",
"tokio", "tokio",
"tower", "tower",
"tower-cookies",
"tower-http", "tower-http",
"tower-layer", "tower-layer",
"tower-service", "tower-service",
@ -1560,6 +1572,23 @@ 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"

View file

@ -25,6 +25,7 @@ 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"] }

View file

@ -13,12 +13,13 @@ use std::sync::Arc;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use tower_layer::Layer; use tower_layer::Layer;
use tower_service::Service; use tower_service::Service;
use tower_cookies::Cookies;
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::validation::Validation;
use crate::{AuthError, RefreshStrategy}; use crate::{AuthError, layer, RefreshStrategy};
/// Authorizer Layer builder /// Authorizer Layer builder
/// ///
@ -32,6 +33,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,
} }
/// authorization layer builder /// authorization layer builder
@ -46,6 +48,7 @@ where
refresh: Default::default(), refresh: Default::default(),
claims_checker: None, claims_checker: None,
validation: None, validation: None,
jwt_source: JwtSource::Bearer,
} }
} }
@ -56,6 +59,7 @@ where
refresh: Default::default(), refresh: Default::default(),
claims_checker: None, claims_checker: None,
validation: None, validation: None,
jwt_source: JwtSource::Bearer,
} }
} }
@ -66,6 +70,7 @@ where
refresh: Default::default(), refresh: Default::default(),
claims_checker: None, claims_checker: None,
validation: None, validation: None,
jwt_source: JwtSource::Bearer,
} }
} }
@ -86,6 +91,7 @@ where
refresh: Default::default(), refresh: Default::default(),
claims_checker: None, claims_checker: None,
validation: None, validation: None,
jwt_source: JwtSource::Bearer,
} }
} }
@ -106,6 +112,7 @@ where
refresh: Default::default(), refresh: Default::default(),
claims_checker: None, claims_checker: None,
validation: None, validation: None,
jwt_source: JwtSource::Bearer,
} }
} }
@ -126,6 +133,7 @@ where
refresh: Default::default(), refresh: Default::default(),
claims_checker: None, claims_checker: None,
validation: None, validation: None,
jwt_source: JwtSource::Bearer,
} }
} }
@ -164,14 +172,19 @@ where
self self
} }
pub fn jwt_source(mut self, src: JwtSource) -> JwtAuthorizer<C> {
self.jwt_source = src;
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 val = self.validation.unwrap_or_default(); let val = self.validation.unwrap_or_default();
let auth = Arc::new(Authorizer::build(&self.key_source_type, self.claims_checker, self.refresh, val).await?); 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, self.jwt_source))
} }
} }
/// Trait for authorizing requests. /// Trait for authorizing requests.
pub trait AsyncAuthorizer<B> { pub trait AsyncAuthorizer<B> {
type RequestBody; type RequestBody;
@ -196,10 +209,24 @@ where
fn authorize(&self, mut request: Request<B>) -> Self::Future { fn authorize(&self, mut request: Request<B>) -> Self::Future {
let authorizer = self.auth.clone(); let authorizer = self.auth.clone();
let h = request.headers(); let h = request.headers();
let token = match &self.jwt_source {
layer::JwtSource::Bearer => {
let bearer_o : Option<Authorization<Bearer>> = h.typed_get(); let bearer_o : Option<Authorization<Bearer>> = h.typed_get();
bearer_o.and_then(|b| Some(String::from(b.0.token())))
}
layer::JwtSource::Cookie(name) => {
if let Some(c) = request.extensions().get::<Cookies>() {
c.get(name.as_str()).and_then(|c| Some(String::from(c.value())))
} 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(bearer) = bearer_o { if let Some(token) = token {
match authorizer.check_auth(bearer.token()).await { match authorizer.check_auth(token.as_str()).await {
Ok(token_data) => { Ok(token_data) => {
// Set `token_data` as a request extension so it can be accessed by other // Set `token_data` as a request extension so it can be accessed by other
// services down the stack. // services down the stack.
@ -224,14 +251,15 @@ where
C: Clone + DeserializeOwned + Send, C: Clone + DeserializeOwned + Send,
{ {
auth: Arc<Authorizer<C>>, auth: Arc<Authorizer<C>>,
jwt_source: JwtSource,
} }
impl<C> AsyncAuthorizationLayer<C> impl<C> AsyncAuthorizationLayer<C>
where where
C: Clone + DeserializeOwned + Send, C: Clone + DeserializeOwned + Send,
{ {
pub fn new(auth: Arc<Authorizer<C>>) -> AsyncAuthorizationLayer<C> { pub fn new(auth: Arc<Authorizer<C>>, jwt_source: JwtSource) -> AsyncAuthorizationLayer<C> {
Self { auth } Self { auth, jwt_source }
} }
} }
@ -242,12 +270,18 @@ where
type Service = AsyncAuthorizationService<S, C>; type Service = AsyncAuthorizationService<S, C>;
fn layer(&self, inner: S) -> Self::Service { fn layer(&self, inner: S) -> Self::Service {
AsyncAuthorizationService::new(inner, self.auth.clone()) AsyncAuthorizationService::new(inner, self.auth.clone(), self.jwt_source.clone())
} }
} }
// ---------- AsyncAuthorizationService -------- // ---------- AsyncAuthorizationService --------
#[derive(Clone)]
pub enum JwtSource{
Bearer,
Cookie(String),
}
#[derive(Clone)] #[derive(Clone)]
pub struct AsyncAuthorizationService<S, C> pub struct AsyncAuthorizationService<S, C>
where where
@ -255,6 +289,7 @@ where
{ {
pub inner: S, pub inner: S,
pub auth: Arc<Authorizer<C>>, pub auth: Arc<Authorizer<C>>,
pub jwt_source: JwtSource,
} }
impl<S, C> AsyncAuthorizationService<S, C> impl<S, C> AsyncAuthorizationService<S, C>
@ -283,8 +318,8 @@ where
/// Authorize requests using a custom scheme. /// Authorize requests using a custom scheme.
/// ///
/// The `Authorization` header is required to have the value provided. /// The `Authorization` header is required to have the value provided.
pub fn new(inner: S, auth: Arc<Authorizer<C>>) -> AsyncAuthorizationService<S, C> { pub fn new(inner: S, auth: Arc<Authorizer<C>>, jwt_source: JwtSource) -> AsyncAuthorizationService<S, C> {
Self { inner, auth } Self { inner, auth , jwt_source }
} }
} }