axum_extra/extract/cookie/
private.rs

1use super::{cookies_from_request, set_cookies, Cookie, Key};
2use axum::{
3    extract::{FromRef, FromRequestParts},
4    response::{IntoResponse, IntoResponseParts, Response, ResponseParts},
5};
6use cookie::PrivateJar;
7use http::{request::Parts, HeaderMap};
8use std::{convert::Infallible, fmt, marker::PhantomData};
9
10/// Extractor that grabs private cookies from the request and manages the jar.
11///
12/// All cookies will be private and encrypted with a [`Key`]. This makes it suitable for storing
13/// private data.
14///
15/// Note that methods like [`PrivateCookieJar::add`], [`PrivateCookieJar::remove`], etc updates the
16/// [`PrivateCookieJar`] and returns it. This value _must_ be returned from the handler as part of
17/// the response for the changes to be propagated.
18///
19/// # Example
20///
21/// ```rust
22/// use axum::{
23///     Router,
24///     routing::{post, get},
25///     extract::FromRef,
26///     response::{IntoResponse, Redirect},
27///     http::StatusCode,
28/// };
29/// use axum_extra::{
30///     TypedHeader,
31///     headers::authorization::{Authorization, Bearer},
32///     extract::cookie::{PrivateCookieJar, Cookie, Key},
33/// };
34///
35/// async fn set_secret(
36///     jar: PrivateCookieJar,
37/// ) -> (PrivateCookieJar, Redirect) {
38///     let updated_jar = jar.add(Cookie::new("secret", "secret-data"));
39///     (updated_jar, Redirect::to("/get"))
40/// }
41///
42/// async fn get_secret(jar: PrivateCookieJar) {
43///     if let Some(data) = jar.get("secret") {
44///         // ...
45///     }
46/// }
47///
48/// // our application state
49/// #[derive(Clone)]
50/// struct AppState {
51///     // that holds the key used to encrypt cookies
52///     key: Key,
53/// }
54///
55/// // this impl tells `PrivateCookieJar` how to access the key from our state
56/// impl FromRef<AppState> for Key {
57///     fn from_ref(state: &AppState) -> Self {
58///         state.key.clone()
59///     }
60/// }
61///
62/// let state = AppState {
63///     // Generate a secure key
64///     //
65///     // You probably don't wanna generate a new one each time the app starts though
66///     key: Key::generate(),
67/// };
68///
69/// let app = Router::new()
70///     .route("/set", post(set_secret))
71///     .route("/get", get(get_secret))
72///     .with_state(state);
73/// # let _: axum::Router = app;
74/// ```
75///
76/// If you have been using `Arc<AppState>` you cannot implement `FromRef<Arc<AppState>> for Key`.
77/// You can use a new type instead:
78///
79/// ```rust
80/// # use axum::extract::FromRef;
81/// # use axum_extra::extract::cookie::{PrivateCookieJar, Cookie, Key};
82/// use std::sync::Arc;
83/// use std::ops::Deref;
84///
85/// #[derive(Clone)]
86/// struct AppState(Arc<InnerState>);
87///
88/// // deref so you can still access the inner fields easily
89/// impl Deref for AppState {
90///     type Target = InnerState;
91///
92///     fn deref(&self) -> &Self::Target {
93///         &self.0
94///     }
95/// }
96///
97/// struct InnerState {
98///     key: Key
99/// }
100///
101/// impl FromRef<AppState> for Key {
102///     fn from_ref(state: &AppState) -> Self {
103///         state.0.key.clone()
104///     }
105/// }
106/// ```
107pub struct PrivateCookieJar<K = Key> {
108    jar: cookie::CookieJar,
109    key: Key,
110    // The key used to extract the key. Allows users to use multiple keys for different
111    // jars. Maybe a library wants its own key.
112    _marker: PhantomData<K>,
113}
114
115impl<K> fmt::Debug for PrivateCookieJar<K> {
116    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117        f.debug_struct("PrivateCookieJar")
118            .field("jar", &self.jar)
119            .field("key", &"REDACTED")
120            .finish()
121    }
122}
123
124impl<S, K> FromRequestParts<S> for PrivateCookieJar<K>
125where
126    S: Send + Sync,
127    K: FromRef<S> + Into<Key>,
128{
129    type Rejection = Infallible;
130
131    async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
132        let k = K::from_ref(state);
133        let key = k.into();
134        let PrivateCookieJar {
135            jar,
136            key,
137            _marker: _,
138        } = PrivateCookieJar::from_headers(&parts.headers, key);
139        Ok(PrivateCookieJar {
140            jar,
141            key,
142            _marker: PhantomData,
143        })
144    }
145}
146
147impl PrivateCookieJar {
148    /// Create a new `PrivateCookieJar` from a map of request headers.
149    ///
150    /// The valid cookies in `headers` will be added to the jar.
151    ///
152    /// This is intended to be used in middleware and other where places it might be difficult to
153    /// run extractors. Normally you should create `PrivateCookieJar`s through [`FromRequestParts`].
154    ///
155    /// [`FromRequestParts`]: axum::extract::FromRequestParts
156    pub fn from_headers(headers: &HeaderMap, key: Key) -> Self {
157        let mut jar = cookie::CookieJar::new();
158        let mut private_jar = jar.private_mut(&key);
159        for cookie in cookies_from_request(headers) {
160            if let Some(cookie) = private_jar.decrypt(cookie) {
161                private_jar.add_original(cookie);
162            }
163        }
164
165        Self {
166            jar,
167            key,
168            _marker: PhantomData,
169        }
170    }
171
172    /// Create a new empty `PrivateCookieJarIter`.
173    ///
174    /// This is intended to be used in middleware and other places where it might be difficult to
175    /// run extractors. Normally you should create `PrivateCookieJar`s through [`FromRequestParts`].
176    ///
177    /// [`FromRequestParts`]: axum::extract::FromRequestParts
178    pub fn new(key: Key) -> Self {
179        Self {
180            jar: Default::default(),
181            key,
182            _marker: PhantomData,
183        }
184    }
185}
186
187impl<K> PrivateCookieJar<K> {
188    /// Get a cookie from the jar.
189    ///
190    /// If the cookie exists and can be decrypted then it is returned in plaintext.
191    ///
192    /// # Example
193    ///
194    /// ```rust
195    /// use axum_extra::extract::cookie::PrivateCookieJar;
196    /// use axum::response::IntoResponse;
197    ///
198    /// async fn handle(jar: PrivateCookieJar) {
199    ///     let value: Option<String> = jar
200    ///         .get("foo")
201    ///         .map(|cookie| cookie.value().to_owned());
202    /// }
203    /// ```
204    pub fn get(&self, name: &str) -> Option<Cookie<'static>> {
205        self.private_jar().get(name)
206    }
207
208    /// Remove a cookie from the jar.
209    ///
210    /// # Example
211    ///
212    /// ```rust
213    /// use axum_extra::extract::cookie::{PrivateCookieJar, Cookie};
214    /// use axum::response::IntoResponse;
215    ///
216    /// async fn handle(jar: PrivateCookieJar) -> PrivateCookieJar {
217    ///     jar.remove(Cookie::from("foo"))
218    /// }
219    /// ```
220    #[must_use]
221    pub fn remove<C: Into<Cookie<'static>>>(mut self, cookie: C) -> Self {
222        self.private_jar_mut().remove(cookie);
223        self
224    }
225
226    /// Add a cookie to the jar.
227    ///
228    /// The value will automatically be percent-encoded.
229    ///
230    /// # Example
231    ///
232    /// ```rust
233    /// use axum_extra::extract::cookie::{PrivateCookieJar, Cookie};
234    /// use axum::response::IntoResponse;
235    ///
236    /// async fn handle(jar: PrivateCookieJar) -> PrivateCookieJar {
237    ///     jar.add(Cookie::new("foo", "bar"))
238    /// }
239    /// ```
240    #[must_use]
241    #[allow(clippy::should_implement_trait)]
242    pub fn add<C: Into<Cookie<'static>>>(mut self, cookie: C) -> Self {
243        self.private_jar_mut().add(cookie);
244        self
245    }
246
247    /// Authenticates and decrypts `cookie`, returning the plaintext version if decryption succeeds
248    /// or `None` otherwise.
249    pub fn decrypt(&self, cookie: Cookie<'static>) -> Option<Cookie<'static>> {
250        self.private_jar().decrypt(cookie)
251    }
252
253    /// Get an iterator over all cookies in the jar.
254    ///
255    /// Only cookies with valid authenticity and integrity are yielded by the iterator.
256    pub fn iter(&self) -> impl Iterator<Item = Cookie<'static>> + '_ {
257        PrivateCookieJarIter {
258            jar: self,
259            iter: self.jar.iter(),
260        }
261    }
262
263    fn private_jar(&self) -> PrivateJar<&'_ cookie::CookieJar> {
264        self.jar.private(&self.key)
265    }
266
267    fn private_jar_mut(&mut self) -> PrivateJar<&'_ mut cookie::CookieJar> {
268        self.jar.private_mut(&self.key)
269    }
270}
271
272impl<K> IntoResponseParts for PrivateCookieJar<K> {
273    type Error = Infallible;
274
275    fn into_response_parts(self, mut res: ResponseParts) -> Result<ResponseParts, Self::Error> {
276        set_cookies(self.jar, res.headers_mut());
277        Ok(res)
278    }
279}
280
281impl<K> IntoResponse for PrivateCookieJar<K> {
282    fn into_response(self) -> Response {
283        (self, ()).into_response()
284    }
285}
286
287struct PrivateCookieJarIter<'a, K> {
288    jar: &'a PrivateCookieJar<K>,
289    iter: cookie::Iter<'a>,
290}
291
292impl<K> Iterator for PrivateCookieJarIter<'_, K> {
293    type Item = Cookie<'static>;
294
295    fn next(&mut self) -> Option<Self::Item> {
296        loop {
297            let cookie = self.iter.next()?;
298
299            if let Some(cookie) = self.jar.get(cookie.name()) {
300                return Some(cookie);
301            }
302        }
303    }
304}
305
306impl<K> Clone for PrivateCookieJar<K> {
307    fn clone(&self) -> Self {
308        Self {
309            jar: self.jar.clone(),
310            key: self.key.clone(),
311            _marker: self._marker,
312        }
313    }
314}