diff --git a/jwt-authorizer/src/error.rs b/jwt-authorizer/src/error.rs index 6c0b10b..bde8118 100644 --- a/jwt-authorizer/src/error.rs +++ b/jwt-authorizer/src/error.rs @@ -1,11 +1,12 @@ use axum::{ http::StatusCode, - response::{IntoResponse, Response}, + response::{IntoResponse, Response}, body::{self, Empty}, }; +use http::header; use jsonwebtoken::Algorithm; use thiserror::Error; -use tracing::log::warn; +use tracing::{log::warn, debug}; #[derive(Debug, Error)] pub enum InitError { @@ -36,8 +37,8 @@ pub enum AuthError { #[error("Invalid Key Algorithm {0:?}")] InvalidKeyAlg(Algorithm), - // #[error(transparent)] - // InvalidTokenHeader(#[from] TypedHeaderRejection), + #[error("Missing Token")] + MissingToken(), #[error(transparent)] InvalidToken(#[from] jsonwebtoken::errors::Error), @@ -46,23 +47,72 @@ pub enum AuthError { InvalidClaims(), } +/// (https://datatracker.ietf.org/doc/html/rfc6750#section-3.1) impl IntoResponse for AuthError { - fn into_response(self) -> Response { - // TODO: add error code (https://datatracker.ietf.org/doc/html/rfc6750#section-3.1) + fn into_response(self) -> Response { warn!("AuthError: {}", &self); - let (status, error_message) = match self { - AuthError::JwksRefreshError(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()), - AuthError::InvalidKid(msg) => (StatusCode::UNAUTHORIZED, msg), - // AuthError::InvalidTokenHeader(_) => (StatusCode::BAD_REQUEST, self.to_string()), - AuthError::InvalidToken(_) => (StatusCode::UNAUTHORIZED, self.to_string()), - AuthError::InvalidKey(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg), - AuthError::JwksSerialisationError(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()), - AuthError::InvalidKeyAlg(_) => (StatusCode::BAD_REQUEST, self.to_string()), - AuthError::InvalidClaims() => (StatusCode::UNAUTHORIZED, self.to_string()), + let resp = match self { + AuthError::JwksRefreshError(err) => { + tracing::error!("AuthErrors::JwksRefreshError: {}", err); + let mut res = Response::new(body::boxed(Empty::new())); + *res.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; + res + }, + AuthError::InvalidKey(err) => { + tracing::error!("AuthErrors::InvalidKey: {}", err); + let mut res = Response::new(body::boxed(Empty::new())); + *res.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; + res + }, + AuthError::JwksSerialisationError(err) => { + tracing::error!("AuthErrors::JwksSerialisationError: {}", err); + let mut res = Response::new(body::boxed(Empty::new())); + *res.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; + res + }, + AuthError::InvalidKeyAlg(err) => { + debug!("AuthErrors::InvalidKeyAlg: {:?}", err); + let mut res = Response::new(body::boxed(Empty::new())); + *res.status_mut() = StatusCode::UNAUTHORIZED; + res.headers_mut().insert(header::WWW_AUTHENTICATE, "Bearer error=\"invalid_token\", error_description=\"invalid key algorithm\"".parse().unwrap()); + res + }, + AuthError::InvalidKid(err) => { + debug!("AuthErrors::InvalidKid: {}", err); + let mut res = Response::new(body::boxed(Empty::new())); + *res.status_mut() = StatusCode::UNAUTHORIZED; + res.headers_mut().insert(header::WWW_AUTHENTICATE, "Bearer error=\"invalid_token\", error_description=\"invalid kid\"".parse().unwrap()); + res + }, + AuthError::InvalidToken(err) => { + debug!("AuthErrors::InvalidToken: {}", err); + let mut res = Response::new(body::boxed(Empty::new())); + *res.status_mut() = StatusCode::UNAUTHORIZED; + res.headers_mut().insert(header::WWW_AUTHENTICATE, "Bearer error=\"invalid_token\"".parse().unwrap()); + res + }, + AuthError::MissingToken() => { + // WWW-Authenticate: Bearer realm="example" + debug!("AuthErrors::MissingToken"); + let mut res = Response::new(body::boxed(Empty::new())); + *res.status_mut() = StatusCode::UNAUTHORIZED; + res.headers_mut().insert(header::WWW_AUTHENTICATE, "Bearer".parse().unwrap()); + res + }, + AuthError::InvalidClaims() => { + // WWW-Authenticate: Bearer error="insufficient_scope" + debug!("AuthErrors::InvalidClaims"); + let mut res = Response::new(body::boxed(Empty::new())); + *res.status_mut() = StatusCode::UNAUTHORIZED; + res.headers_mut().insert(header::WWW_AUTHENTICATE, "Bearer error=\"insufficient_scope\"".parse().unwrap()); + + res + }, }; - let body = axum::Json(serde_json::json!({ - "error": error_message, - })); - (status, body).into_response() + // let body = axum::Json(serde_json::json!({ + // "error": error_message, + // })); + + resp } } diff --git a/jwt-authorizer/src/layer.rs b/jwt-authorizer/src/layer.rs index 2c7b40c..9899dbd 100644 --- a/jwt-authorizer/src/layer.rs +++ b/jwt-authorizer/src/layer.rs @@ -5,7 +5,6 @@ use futures_core::ready; use futures_util::future::BoxFuture; use headers::authorization::Bearer; use headers::{Authorization, HeaderMapExt}; -use http::StatusCode; use pin_project::pin_project; use serde::de::DeserializeOwned; use std::future::Future; @@ -15,6 +14,7 @@ use std::task::{Context, Poll}; use tower_layer::Layer; use tower_service::Service; +use crate::AuthError; use crate::authorizer::{Authorizer, FnClaimsChecker, KeySourceType}; use crate::error::InitError; @@ -145,7 +145,7 @@ where } } } else { - Err((StatusCode::UNAUTHORIZED).into_response()) + Err(AuthError::MissingToken().into_response()) } }) } diff --git a/jwt-authorizer/src/tests.rs b/jwt-authorizer/src/tests.rs index b77b645..c0aafd2 100644 --- a/jwt-authorizer/src/tests.rs +++ b/jwt-authorizer/src/tests.rs @@ -6,6 +6,7 @@ mod tests { http::{Request, StatusCode}, routing::get, Router, response::Response, }; + use http::header; use serde::Deserialize; use tower::ServiceExt; @@ -48,7 +49,9 @@ mod tests { .unwrap(); assert_eq!(response.status(), StatusCode::UNAUTHORIZED); - // TODO: check error code (https://datatracker.ietf.org/doc/html/rfc6750#section-3.1) + + assert!(response.headers().get(header::WWW_AUTHENTICATE).is_some(), "Must have a WWW-Authenticate header!"); + assert_eq!(response.headers().get(header::WWW_AUTHENTICATE).unwrap(), &"Bearer"); // TODO: realm="example" } #[tokio::test]