actix_jwt_auth_middleware/
token_signer.rs

1use crate::AuthError;
2use crate::AuthResult;
3
4use std::marker::PhantomData;
5use std::time::Duration;
6
7use actix_web::cookie::Cookie;
8use actix_web::http::header::HeaderValue;
9use chrono::TimeDelta;
10use derive_builder::Builder;
11use jwt_compact::Algorithm;
12use jwt_compact::AlgorithmExt;
13use jwt_compact::Claims as TokenClaims;
14use jwt_compact::Header;
15use jwt_compact::TimeOptions;
16use serde::Serialize;
17
18/**
19    The [`TokenSigner`] is a convenience struct,
20    which holds configuration values as well as a private key for generation JWTs.
21
22    For example, the [`crate::Authority`] uses it to automatically refresh the `access`/`refresh` token.
23    # Example
24    ```rust
25    # use std::time::Duration;
26    # use actix_jwt_auth_middleware::{TokenSigner, AuthError};
27    # use serde::Serialize;
28    # use ed25519_compact::KeyPair;
29    # use jwt_compact::{alg::Ed25519, TimeOptions};
30    #[derive(Serialize, Clone)]
31    struct User {
32        id: u32
33    }
34
35    let KeyPair {
36        sk: secret_key,
37        ..
38    } = KeyPair::generate();
39
40    let token_signer = TokenSigner::<User, _>::new()
41        .signing_key(secret_key)
42        .access_token_name("my_access_token")
43        // makes every refresh token generated be valid for 2 hours
44        .refresh_token_lifetime(Duration::from_secs(120 * 60))
45        // generated tokens can still be used up to 10 seconds after they expired
46        .time_options(TimeOptions::from_leeway(chrono::Duration::seconds(10)))
47        .algorithm(Ed25519)
48        .build().unwrap();
49
50    let cookie = token_signer.create_access_cookie(&User{
51        id: 1
52    })?;
53    # Ok::<(), AuthError>(())
54    ```
55    Please refer to the [`TokenSignerBuilder`] for a detailed description of Options available on this struct.
56*/
57#[derive(Builder)]
58#[builder(pattern = "owned")]
59pub struct TokenSigner<Claims, Algo>
60where
61    Algo: Algorithm,
62{
63    /**
64        The name of the future access tokens.
65
66        For example, the name of the cookie generated in [`TokenSigner::create_access_cookie`].
67
68        Defaults to `"access_token"`
69    */
70    #[builder(default = "\"access_token\".into()")]
71    #[builder(setter(into))]
72    pub(crate) access_token_name: String,
73    /**
74        The lifetime duration of the access token.
75
76        Defaults to `Duration::from_secs(60)`
77    */
78    #[builder(default = "Duration::from_secs(60)")]
79    access_token_lifetime: Duration,
80    /**
81        The name of the future refresh tokens.
82
83        For example, the name of the cookie generated in [`TokenSigner::create_refresh_cookie`].
84
85        Defaults to `"refresh_token"`
86    */
87    #[builder(default = "\"refresh_token\".into()")]
88    #[builder(setter(into))]
89    pub(crate) refresh_token_name: String,
90    /**
91        The lifetime duration of the refresh token.
92
93        Defaults to `Duration::from_secs(30 * 60)`
94    */
95    #[builder(default = "Duration::from_secs(30 * 60)")]
96    refresh_token_lifetime: Duration,
97    /**
98        JWT Header used in the creation of access and refresh tokens.
99
100        Please refer to the structs own documentation for more details.
101
102        Defaults to `Header::default()`
103    */
104    #[builder(default)]
105    header: Header,
106    /**
107        The Cryptographic signing algorithm used in the process of creation of access and refresh tokens.
108
109        Please referee to the [`Supported algorithms`](https://docs.rs/jwt-compact/latest/jwt_compact/#supported-algorithms) section of the `jwt-compact` crate
110        for a comprehensive list of the supported algorithms.
111    */
112    pub(crate) algorithm: Algo,
113    /**
114        Key used to sign tokens.
115    */
116    signing_key: Algo::SigningKey,
117    /**
118        Used in the creating of the `token`, the current time stamp is taken from this.
119
120        Please refer to the structs own documentation for more details.
121
122        Defaults to `TimeOptions::from_leeway(Duration::seconds(0))`
123    */
124    #[builder(default = "TimeOptions::from_leeway(TimeDelta::try_seconds(0).unwrap())")]
125    pub(crate) time_options: TimeOptions,
126    #[doc(hidden)]
127    #[builder(setter(skip), default = "PhantomData")]
128    claims_marker: PhantomData<Claims>,
129}
130
131impl<Claims, Algorithm> TokenSigner<Claims, Algorithm>
132where
133    Algorithm: jwt_compact::Algorithm + Clone,
134    Claims: Serialize,
135{
136    /**
137        Returns a new [`TokenSignerBuilder`]
138    */
139    #[allow(clippy::new_ret_no_self)]
140    pub fn new() -> TokenSignerBuilder<Claims, Algorithm> {
141        TokenSignerBuilder::create_empty()
142    }
143
144    /**
145        Returns the value of the `access_token_name` field on this struct.
146    */
147    pub fn access_token_name(&self) -> &str {
148        &self.access_token_name
149    }
150
151    /**
152        Returns the value of the `refresh_token_name` field on this struct.
153    */
154    pub fn refresh_token_name(&self) -> &str {
155        &self.refresh_token_name
156    }
157
158    /**
159        Creates a refresh token header value.
160
161        Internally it calls [`Self::create_header_value`] while passing the previously defined
162        `refresh_token_lifetime` value on this struct.
163    */
164    #[inline]
165    pub fn create_refresh_header_value(&self, claims: &Claims) -> AuthResult<HeaderValue> {
166        self.create_header_value(claims, self.refresh_token_lifetime)
167    }
168
169    /**
170        Creates a access token header value.
171
172        Internally it calls [`Self::create_header_value`] while passing the previously defined
173        `access_token_lifetime` value on this struct.
174    */
175    #[inline]
176    pub fn create_access_header_value(&self, claims: &Claims) -> AuthResult<HeaderValue> {
177        self.create_header_value(claims, self.access_token_lifetime)
178    }
179
180    /**
181        Creates a token and wraps it in a [`HeaderValue`].
182
183        Internally it calls [`Self::create_signed_token`] while
184        passing the `claims` as well as the `token_lifetime`.
185    */
186    pub fn create_header_value(
187        &self,
188        claims: &Claims,
189        token_lifetime: Duration,
190    ) -> AuthResult<HeaderValue> {
191        let token = self.create_signed_token(claims, token_lifetime)?;
192        Ok(HeaderValue::from_str(&token)
193            .expect("Token should not contain ASCII characters (33-127)"))
194    }
195
196    /**
197        Creates a Bearer [`HeaderValue`] wrapping a token.
198
199        This value is typically set as the Authorization header, also known as Bearer Authentication.
200
201        Internally it it calls [`Self::create_signed_token`]
202        while passing the previously defined value of the `access_token_lifetime` on this struct.
203    */
204    pub fn create_bearer_header_value(&self, claims: &Claims) -> AuthResult<HeaderValue> {
205        let token = self.create_signed_token(claims, self.access_token_lifetime)?;
206        Ok(HeaderValue::from_str(&format!("Bearer {token}"))
207            .expect("Token should not contain ASCII characters (33-127)"))
208    }
209
210    /**
211        Creates a access token cookie.
212
213        Internally it calls [`Self::create_cookie`] while passing the previously defined
214        `access_token_name` and `access_token_lifetime` values on this struct.
215    */
216    #[inline]
217    pub fn create_access_cookie(&self, claims: &Claims) -> AuthResult<Cookie<'static>> {
218        self.create_cookie(claims, &self.access_token_name, self.access_token_lifetime)
219    }
220
221    /**
222        Creates a refresh token cookie.
223
224        Internally it calls [`Self::create_cookie`] while passing the previously defined
225        `refresh_token_name` and `refresh_token_lifetime` values on this struct.
226    */
227    #[inline]
228    pub fn create_refresh_cookie(&self, claims: &Claims) -> AuthResult<Cookie<'static>> {
229        self.create_cookie(
230            claims,
231            &self.refresh_token_name,
232            self.refresh_token_lifetime,
233        )
234    }
235
236    /**
237        Creates a token and wraps it in a [`Cookie`].
238
239        Internally it calls [`Self::create_signed_token`] while
240        passing the `claims` as well as the `token_lifetime`.
241
242        * `cookie_name` the name of the resulting cookie
243    */
244    pub fn create_cookie(
245        &self,
246        claims: &Claims,
247        cookie_name: &str,
248        token_lifetime: Duration,
249    ) -> AuthResult<Cookie<'static>> {
250        let token = self.create_signed_token(claims, token_lifetime)?;
251        Ok(Cookie::build(cookie_name.to_string(), token)
252            .secure(true)
253            .finish())
254    }
255
256    /**
257        Creates a signed token using the previously defined
258        [`TimeOptions`], [`Header`] and [`jwt_compact::Algorithm::SigningKey`]
259        values on this struct.
260
261        * `claims` reference to an object of the generic type `Claims` which will be incorporated inside of the JWT string
262
263        * `token_lifetime` duration for which the token is valid for
264    */
265    pub fn create_signed_token(
266        &self,
267        claims: &Claims,
268        token_lifetime: Duration,
269    ) -> AuthResult<String> {
270        let token_claims = TokenClaims::new(claims).set_duration_and_issuance(
271            &self.time_options,
272            TimeDelta::from_std(token_lifetime).unwrap(),
273        );
274
275        self.algorithm
276            .token(&self.header, &token_claims, &self.signing_key)
277            .map_err(AuthError::TokenCreation)
278    }
279}
280
281impl<Claims, Algo: Clone> Clone for TokenSigner<Claims, Algo>
282where
283    Algo: Algorithm,
284    Algo::SigningKey: Clone,
285{
286    #[inline]
287    fn clone(&self) -> TokenSigner<Claims, Algo> {
288        TokenSigner {
289            access_token_name: Clone::clone(&self.access_token_name),
290            access_token_lifetime: Clone::clone(&self.access_token_lifetime),
291            refresh_token_name: Clone::clone(&self.refresh_token_name),
292            refresh_token_lifetime: Clone::clone(&self.refresh_token_lifetime),
293            header: Clone::clone(&self.header),
294            algorithm: Clone::clone(&self.algorithm),
295            signing_key: Clone::clone(&self.signing_key),
296            time_options: Clone::clone(&self.time_options),
297            claims_marker: Clone::clone(&self.claims_marker),
298        }
299    }
300}