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}