tower_async_http/auth/
add_authorization.rs

1//! Add authorization to requests using the [`Authorization`] header.
2//!
3//! [`Authorization`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization
4//!
5//! # Example
6//!
7//! ```
8//! use tower_async_http::validate_request::{ValidateRequestHeader, ValidateRequestHeaderLayer};
9//! use tower_async_http::auth::AddAuthorizationLayer;
10//! use http_body_util::Full;
11//! use bytes::Bytes;
12//! use http::{Request, Response, StatusCode, header::AUTHORIZATION};
13//! use tower_async::{Service, ServiceExt, ServiceBuilder, service_fn, BoxError};
14//! # async fn handle(request: Request<Full<Bytes>>) -> Result<Response<Full<Bytes>>, BoxError> {
15//! #     Ok(Response::new(Full::default()))
16//! # }
17//!
18//! # #[tokio::main]
19//! # async fn main() -> Result<(), BoxError> {
20//! # let service_that_requires_auth = ValidateRequestHeader::basic(
21//! #     tower_async::service_fn(handle),
22//! #     "username",
23//! #     "password",
24//! # );
25//! let mut client = ServiceBuilder::new()
26//!     // Use basic auth with the given username and password
27//!     .layer(AddAuthorizationLayer::basic("username", "password"))
28//!     .service(service_that_requires_auth);
29//!
30//! // Make a request, we don't have to add the `Authorization` header manually
31//! let response = client
32//!     .call(Request::new(Full::<Bytes>::default()))
33//!     .await?;
34//!
35//! assert_eq!(StatusCode::OK, response.status());
36//! # Ok(())
37//! # }
38//! ```
39
40use base64::Engine as _;
41use http::{HeaderValue, Request, Response};
42use std::convert::TryFrom;
43use tower_async_layer::Layer;
44use tower_async_service::Service;
45
46const BASE64: base64::engine::GeneralPurpose = base64::engine::general_purpose::STANDARD;
47
48/// Layer that applies [`AddAuthorization`] which adds authorization to all requests using the
49/// [`Authorization`] header.
50///
51/// See the [module docs](crate::auth::add_authorization) for an example.
52///
53/// You can also use [`SetRequestHeader`] if you have a use case that isn't supported by this
54/// middleware.
55///
56/// [`Authorization`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization
57/// [`SetRequestHeader`]: crate::set_header::SetRequestHeader
58#[derive(Debug, Clone)]
59pub struct AddAuthorizationLayer {
60    value: HeaderValue,
61}
62
63impl AddAuthorizationLayer {
64    /// Authorize requests using a username and password pair.
65    ///
66    /// The `Authorization` header will be set to `Basic {credentials}` where `credentials` is
67    /// `base64_encode("{username}:{password}")`.
68    ///
69    /// Since the username and password is sent in clear text it is recommended to use HTTPS/TLS
70    /// with this method. However use of HTTPS/TLS is not enforced by this middleware.
71    pub fn basic(username: &str, password: &str) -> Self {
72        let encoded = BASE64.encode(format!("{}:{}", username, password));
73        let value = HeaderValue::try_from(format!("Basic {}", encoded)).unwrap();
74        Self { value }
75    }
76
77    /// Authorize requests using a "bearer token". Commonly used for OAuth 2.
78    ///
79    /// The `Authorization` header will be set to `Bearer {token}`.
80    ///
81    /// # Panics
82    ///
83    /// Panics if the token is not a valid [`HeaderValue`].
84    pub fn bearer(token: &str) -> Self {
85        let value =
86            HeaderValue::try_from(format!("Bearer {}", token)).expect("token is not valid header");
87        Self { value }
88    }
89
90    /// Mark the header as [sensitive].
91    ///
92    /// This can for example be used to hide the header value from logs.
93    ///
94    /// [sensitive]: https://docs.rs/http/latest/http/header/struct.HeaderValue.html#method.set_sensitive
95    #[allow(clippy::wrong_self_convention)]
96    pub fn as_sensitive(mut self, sensitive: bool) -> Self {
97        self.value.set_sensitive(sensitive);
98        self
99    }
100}
101
102impl<S> Layer<S> for AddAuthorizationLayer {
103    type Service = AddAuthorization<S>;
104
105    fn layer(&self, inner: S) -> Self::Service {
106        AddAuthorization {
107            inner,
108            value: self.value.clone(),
109        }
110    }
111}
112
113/// Middleware that adds authorization all requests using the [`Authorization`] header.
114///
115/// See the [module docs](crate::auth::add_authorization) for an example.
116///
117/// You can also use [`SetRequestHeader`] if you have a use case that isn't supported by this
118/// middleware.
119///
120/// [`Authorization`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization
121/// [`SetRequestHeader`]: crate::set_header::SetRequestHeader
122#[derive(Debug, Clone)]
123pub struct AddAuthorization<S> {
124    inner: S,
125    value: HeaderValue,
126}
127
128impl<S> AddAuthorization<S> {
129    /// Authorize requests using a username and password pair.
130    ///
131    /// The `Authorization` header will be set to `Basic {credentials}` where `credentials` is
132    /// `base64_encode("{username}:{password}")`.
133    ///
134    /// Since the username and password is sent in clear text it is recommended to use HTTPS/TLS
135    /// with this method. However use of HTTPS/TLS is not enforced by this middleware.
136    pub fn basic(inner: S, username: &str, password: &str) -> Self {
137        AddAuthorizationLayer::basic(username, password).layer(inner)
138    }
139
140    /// Authorize requests using a "bearer token". Commonly used for OAuth 2.
141    ///
142    /// The `Authorization` header will be set to `Bearer {token}`.
143    ///
144    /// # Panics
145    ///
146    /// Panics if the token is not a valid [`HeaderValue`].
147    pub fn bearer(inner: S, token: &str) -> Self {
148        AddAuthorizationLayer::bearer(token).layer(inner)
149    }
150
151    define_inner_service_accessors!();
152
153    /// Mark the header as [sensitive].
154    ///
155    /// This can for example be used to hide the header value from logs.
156    ///
157    /// [sensitive]: https://docs.rs/http/latest/http/header/struct.HeaderValue.html#method.set_sensitive
158    #[allow(clippy::wrong_self_convention)]
159    pub fn as_sensitive(mut self, sensitive: bool) -> Self {
160        self.value.set_sensitive(sensitive);
161        self
162    }
163}
164
165impl<S, ReqBody, ResBody> Service<Request<ReqBody>> for AddAuthorization<S>
166where
167    S: Service<Request<ReqBody>, Response = Response<ResBody>>,
168{
169    type Response = S::Response;
170    type Error = S::Error;
171
172    async fn call(&self, mut req: Request<ReqBody>) -> Result<Self::Response, Self::Error> {
173        req.headers_mut()
174            .insert(http::header::AUTHORIZATION, self.value.clone());
175        self.inner.call(req).await
176    }
177}
178
179#[cfg(test)]
180mod tests {
181    use std::convert::Infallible;
182
183    #[allow(unused_imports)]
184    use super::*;
185
186    use crate::test_helpers::Body;
187    use crate::validate_request::ValidateRequestHeaderLayer;
188
189    use http::{Response, StatusCode};
190    use tower_async::{BoxError, Service, ServiceBuilder};
191
192    #[tokio::test]
193    async fn basic() {
194        // service that requires auth for all requests
195        let svc = ServiceBuilder::new()
196            .layer(ValidateRequestHeaderLayer::basic("foo", "bar"))
197            .service_fn(echo);
198
199        // make a client that adds auth
200        let client = AddAuthorization::basic(svc, "foo", "bar");
201
202        let res = client.call(Request::new(Body::empty())).await.unwrap();
203
204        assert_eq!(res.status(), StatusCode::OK);
205    }
206
207    #[tokio::test]
208    async fn token() {
209        // service that requires auth for all requests
210        let svc = ServiceBuilder::new()
211            .layer(ValidateRequestHeaderLayer::bearer("foo"))
212            .service_fn(echo);
213
214        // make a client that adds auth
215        let client = AddAuthorization::bearer(svc, "foo");
216
217        let res = client.call(Request::new(Body::empty())).await.unwrap();
218
219        assert_eq!(res.status(), StatusCode::OK);
220    }
221
222    #[tokio::test]
223    async fn making_header_sensitive() {
224        let svc = ServiceBuilder::new()
225            .layer(ValidateRequestHeaderLayer::bearer("foo"))
226            .service_fn(|request: Request<Body>| async move {
227                let auth = request.headers().get(http::header::AUTHORIZATION).unwrap();
228                assert!(auth.is_sensitive());
229
230                Ok::<_, Infallible>(Response::new(Body::empty()))
231            });
232
233        let client = AddAuthorization::bearer(svc, "foo").as_sensitive(true);
234
235        let res = client.call(Request::new(Body::empty())).await.unwrap();
236
237        assert_eq!(res.status(), StatusCode::OK);
238    }
239
240    async fn echo<Body>(req: Request<Body>) -> Result<Response<Body>, BoxError> {
241        Ok(Response::new(req.into_body()))
242    }
243}