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