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}