actix_jwt_auth_middleware/
authority.rs

1use crate::helper_macros::continue_if_matches_err_variant;
2use crate::helper_macros::make_token_update;
3use crate::helper_macros::pull_from_token_signer;
4use crate::validate::validate_jwt;
5use crate::AuthError;
6use crate::AuthResult;
7use crate::TokenSigner;
8
9use std::marker::PhantomData;
10
11use actix_web::cookie::Cookie;
12use actix_web::dev::ServiceRequest;
13use actix_web::http::header::HeaderMap;
14use actix_web::http::header::HeaderValue;
15use actix_web::http::header::AUTHORIZATION;
16use actix_web::Error as ActixWebError;
17use actix_web::FromRequest;
18use actix_web::Handler;
19use actix_web::HttpMessage;
20use derive_builder::Builder;
21use jwt_compact::Algorithm;
22use jwt_compact::TimeOptions;
23use jwt_compact::Token;
24use jwt_compact::UntrustedToken;
25use jwt_compact::ValidationError::Expired as TokenExpired;
26use serde::de::DeserializeOwned;
27use serde::Serialize;
28
29/*
30    Struct used to signal to the middleware that a cookie needs to be updated
31    after the wrapped service has returned a response.
32*/
33#[doc(hidden)]
34#[derive(Debug)]
35pub struct TokenUpdate {
36    pub(crate) access_cookie: Option<Cookie<'static>>,
37    pub(crate) refresh_cookie: Option<Cookie<'static>>,
38}
39
40/**
41    The [`Authority`] handles the process of authorizing service requests in this crate.
42
43    It holds many configuration options to enable/disable specific authorization methods as well as the automatic renewal of JWTs.
44    # Example
45    ```rust
46    # use actix_jwt_auth_middleware::{Authority, TokenSigner, AuthorityBuilderError};
47    # use jwt_compact::alg::Ed25519;
48    # use ed25519_compact::KeyPair;
49    # let KeyPair {sk: secret_key, pk: public_key} = KeyPair::generate();
50    let authority = Authority::<(), Ed25519, _, _>::new()
51        .refresh_authorizer(|| async move { Ok(()) })
52        .token_signer(Some(
53            TokenSigner::new()
54                .signing_key(secret_key)
55                .algorithm(Ed25519)
56                .build()
57                .expect(""),
58        ))
59        .verifying_key(public_key)
60        .build()?;
61    # Ok::<(), AuthorityBuilderError>(())
62    ```
63    Please refer to the [`AuthorityBuilder`] for a detailed description of options available on this struct.
64*/
65#[derive(Builder, Clone)]
66#[builder(pattern = "owned")]
67pub struct Authority<Claims, Algo, ReAuth, Args>
68where
69    Algo: Algorithm + Clone,
70    Algo::SigningKey: Clone,
71{
72    /**
73        The `refresh_authorizer` is called every time,
74        when a client with an expired access token but a valid refresh token
75        tries to fetch a resource protected by the JWT middleware.
76
77        By returning the `Ok` variant your grand the client permission to get a new access token.
78        In contrast, by returning the `Err` variant you deny the request.
79        The [`actix_web::Error`] returned in this case
80        will be passed along as a wrapped [`AuthError::RefreshAuthorizerDenied`] back to the client
81        (There are options to remap this, for example this crate: [`actix-error-mapper-middleware`](https://github.com/michaelvanstraten/actix-error-mapper-middleware)).
82
83        Since `refresh_authorizer` has to implement the [`Handler`](actix_web::dev::Handler) trait,
84        you are able to access your regular application an request state from within
85        the function. This allows you to perform Database Check etc...
86    */
87    refresh_authorizer: ReAuth,
88    /**
89        Depending on whether a [`TokenSigner`] is set, setting this field will have no affect.
90
91        Defaults to the value of the `access_token_name` field set on the `token_signer`, if the `token_signer` is not set,
92        this defaults to `"access_token"`.
93    */
94    #[builder(
95        default = "pull_from_token_signer!(self, access_token_name, \"access_token\".into())"
96    )]
97    #[builder(setter(into))]
98    pub(crate) access_token_name: String,
99    /**
100        If set to false the clients access token will not be automatically refreshed.
101
102        Defaults to `true`
103    */
104    #[builder(default = "true")]
105    renew_access_token_automatically: bool,
106    /**
107        Depending on whether a [`TokenSigner`] is set, setting this field will have no affect.
108
109        Defaults to the value of the `refresh_token_name` field set on the `token_signer`,
110        if the `token_signer` is not set, this defaults to `"refresh_token"`.
111    */
112    #[builder(
113        default = "pull_from_token_signer!(self, refresh_token_name, \"refresh_token\".into())"
114    )]
115    #[builder(setter(into))]
116    pub(crate) refresh_token_name: String,
117    /**
118        If set to true the clients refresh token will automatically refreshed,
119        this allows clients to basically stay authenticated over a infinite amount of time, so i don't recommend it.
120
121        Defaults to `false`
122    */
123    #[builder(default = "false")]
124    renew_refresh_token_automatically: bool,
125    /**
126        If set to true, the service will look for `access_token_name` and `refresh_token_name` in
127        http headers.
128    */
129    #[builder(default = "false")]
130    enable_header_tokens: bool,
131    /**
132        If set to true, the service will look for the [`Authorization`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization)
133        header in the http headers.
134    */
135    #[builder(default = "false")]
136    enable_authorization_header: bool,
137    /**
138        If set to true, the service will look for `access_token_name` and `refresh_token_name` in
139        in the query parameters.
140    */
141    #[builder(default = "false")]
142    enable_query_tokens: bool,
143    /**
144        If set to true, the service will look for `access_token_name` and `refresh_token_name` in
145        in the cookies of the processed request.
146    */
147    #[builder(default = "true")]
148    enable_cookie_tokens: bool,
149    /**
150        Key used to verify integrity of access and refresh token.
151    */
152    verifying_key: Algo::VerifyingKey,
153    /**
154        The Cryptographic signing algorithm used in the process of creation of access and refresh tokens.
155
156        Please referee to the [`Supported algorithms`](https://docs.rs/jwt-compact/latest/jwt_compact/#supported-algorithms) section of the `jwt-compact` crate for a comprehensive list of the supported algorithms.
157
158        Defaults to the value of the `algorithm` field set on the `token_signer`, if the `token_signer` is not set,
159        this field needs to be set.
160    */
161    #[builder(default = "pull_from_token_signer!(self, algorithm)")]
162    algorithm: Algo,
163    /**
164        Used in the creating of the `token`, the current time stamp is taken from this, but please referee to the Structs documentation.
165
166        Defaults to the value of the `time_options` field set on the `token_signer`, if the `token_signer` is not set,
167        this field needs to be set.
168    */
169    #[builder(default = "pull_from_token_signer!(self, time_options)")]
170    time_options: TimeOptions,
171    /**
172        Not Passing a [`TokenSigner`] struct will make your middleware unable to refresh the access token automatically.
173
174        You will have to provide a algorithm manually in this case because the Authority can not pull it from the `token_signer` field.
175
176        Please referee to the structs own documentation for more details.
177    */
178    #[builder(default = "None")]
179    token_signer: Option<TokenSigner<Claims, Algo>>,
180    #[doc(hidden)]
181    #[builder(setter(skip), default = "PhantomData")]
182    claims_marker: PhantomData<Claims>,
183    #[doc(hidden)]
184    #[builder(setter(skip), default = "PhantomData")]
185    args_marker: PhantomData<Args>,
186}
187
188impl<Claims, Algo, ReAuth, Args> Authority<Claims, Algo, ReAuth, Args>
189where
190    Claims: Serialize + DeserializeOwned + 'static,
191    Algo: Algorithm + Clone,
192    Algo::SigningKey: Clone,
193    ReAuth: Handler<Args, Output = Result<(), ActixWebError>>,
194    Args: FromRequest,
195{
196    /**
197        Returns a new [`AuthorityBuilder`].
198    */
199    #[allow(clippy::new_ret_no_self)]
200    pub fn new() -> AuthorityBuilder<Claims, Algo, ReAuth, Args> {
201        AuthorityBuilder::default()
202    }
203
204    /**
205        Returns a Clone of the `token_signer` field on the Authority.
206    */
207    pub fn token_signer(&self) -> Option<TokenSigner<Claims, Algo>>
208    where
209        TokenSigner<Claims, Algo>: Clone,
210    {
211        self.token_signer.clone()
212    }
213
214    /**
215        Use by the actual middleware, which is hidden from the docs,
216        in order to verify an incoming request and ether hand it of to protected services
217        or deny the request by return a wrapped [`AuthError`].
218    */
219    pub async fn verify_service_request(
220        &self,
221        req: &mut ServiceRequest,
222    ) -> AuthResult<Option<TokenUpdate>> {
223        match self.validate_access_token(req) {
224            Ok(access_token) => {
225                let (_, claims) = access_token.into_parts();
226                req.extensions_mut().insert(claims.custom);
227                Ok(None)
228            }
229            Err(AuthError::TokenValidation(TokenExpired) | AuthError::NoToken)
230                if self.renew_access_token_automatically =>
231            {
232                self.call_refresh_authorizer(req).await?;
233                match (self.validate_refresh_token(req), &self.token_signer) {
234                    (Ok(refresh_token), Some(token_signer)) => {
235                        let (_, claims) = refresh_token.into_parts();
236                        let access_cookie = token_signer.create_access_cookie(&claims.custom)?;
237                        req.extensions_mut().insert(claims.custom);
238                        make_token_update!(access_cookie)
239                    }
240                    (Err(AuthError::TokenValidation(TokenExpired)), Some(token_signer))
241                        if self.renew_refresh_token_automatically =>
242                    {
243                        let claims = extract_claims_unsafe(
244                            req.cookie(&self.refresh_token_name)
245                                .expect("Cookie has to be set in oder to get to this point")
246                                .value(),
247                        );
248                        let access_cookie = token_signer.create_access_cookie(&claims)?;
249                        let refresh_cookie = token_signer.create_refresh_cookie(&claims)?;
250                        req.extensions_mut().insert(claims);
251                        make_token_update!(access_cookie, refresh_cookie)
252                    }
253                    (Ok(_), None) => Err(AuthError::NoTokenSigner),
254                    (Err(err), _) => Err(err),
255                }
256            }
257            Err(err) => Err(err),
258        }
259    }
260}
261
262impl<Claims, Algo, ReAuth, Args> Authority<Claims, Algo, ReAuth, Args>
263where
264    Claims: Serialize + DeserializeOwned + 'static,
265    Algo: Algorithm + Clone,
266    Algo::SigningKey: Clone,
267    ReAuth: Handler<Args, Output = Result<(), ActixWebError>>,
268    Args: FromRequest,
269{
270    #[inline]
271    fn validate_access_token(&self, req: &ServiceRequest) -> AuthResult<Token<Claims>> {
272        self.validate_token(req, &self.access_token_name)
273    }
274
275    #[inline]
276    fn validate_refresh_token(&self, req: &ServiceRequest) -> AuthResult<Token<Claims>> {
277        self.validate_token(req, &self.refresh_token_name)
278    }
279
280    fn validate_token(&self, req: &ServiceRequest, token_name: &str) -> AuthResult<Token<Claims>> {
281        if self.enable_query_tokens {
282            continue_if_matches_err_variant!(
283                self.get_token_from_query(req, token_name),
284                AuthError::NoToken
285            )
286        }
287        if self.enable_header_tokens {
288            continue_if_matches_err_variant!(
289                self.get_token_from_header_value(req.headers(), token_name),
290                AuthError::NoToken
291            )
292        }
293        if self.enable_authorization_header {
294            continue_if_matches_err_variant!(
295                self.get_token_from_authorization_header(req.headers()),
296                AuthError::NoToken
297            )
298        }
299        if self.enable_cookie_tokens {
300            continue_if_matches_err_variant!(
301                self.get_token_from_cookie(req, token_name),
302                AuthError::NoToken
303            )
304        }
305
306        Err(AuthError::NoToken)
307    }
308
309    fn get_token_from_cookie(
310        &self,
311        req: &ServiceRequest,
312        cookie_name: &str,
313    ) -> AuthResult<Token<Claims>> {
314        match req.cookie(cookie_name) {
315            Some(token_value) => validate_jwt(
316                &token_value.value(),
317                &self.algorithm,
318                &self.verifying_key,
319                &self.time_options,
320            ),
321            None => Err(AuthError::NoToken),
322        }
323    }
324
325    fn get_token_from_header_value(
326        &self,
327        header_map: &HeaderMap,
328        header_key: &str,
329    ) -> AuthResult<Token<Claims>> {
330        match header_map.get(header_key).map(HeaderValue::to_str) {
331            Some(Ok(token_value)) => validate_jwt(
332                &token_value,
333                &self.algorithm,
334                &self.verifying_key,
335                &self.time_options,
336            ),
337            Some(_) | None => Err(AuthError::NoToken),
338        }
339    }
340
341    fn get_token_from_authorization_header(
342        &self,
343        header_map: &HeaderMap,
344    ) -> AuthResult<Token<Claims>> {
345        match header_map.get(AUTHORIZATION).map(HeaderValue::to_str) {
346            Some(Ok(header_value)) => {
347                let token_value = if header_value.strip_prefix("Bearer").is_some() {
348                    header_value.trim()
349                } else {
350                    // to-do: better error handling
351                    return Err(AuthError::NoToken);
352                };
353
354                validate_jwt(
355                    &token_value,
356                    &self.algorithm,
357                    &self.verifying_key,
358                    &self.time_options,
359                )
360            }
361            Some(_) | None => Err(AuthError::NoToken),
362        }
363    }
364
365    fn get_token_from_query(
366        &self,
367        req: &ServiceRequest,
368        param_name: &str,
369    ) -> AuthResult<Token<Claims>> {
370        match form_urlencoded::parse(req.query_string().as_bytes())
371            .find(|(query_param_name, _)| param_name.eq(query_param_name))
372        {
373            Some((_, token_value)) => validate_jwt(
374                &token_value,
375                &self.algorithm,
376                &self.verifying_key,
377                &self.time_options,
378            ),
379            None => Err(AuthError::NoToken),
380        }
381    }
382
383    async fn call_refresh_authorizer(&self, req: &mut ServiceRequest) -> AuthResult<()> {
384        let (mut_req, payload) = req.parts_mut();
385        match Args::from_request(mut_req, payload).await {
386            Ok(args) => self
387                .refresh_authorizer
388                .call(args)
389                .await
390                .map_err(AuthError::RefreshAuthorizerDenied),
391            Err(err) => Err(AuthError::RefreshAuthorizerCall(err.into())),
392        }
393    }
394}
395
396#[inline]
397fn extract_claims_unsafe<S, Claims>(token_value: &S) -> Claims
398where
399    S: AsRef<str> + ?Sized,
400    Claims: DeserializeOwned,
401{
402    UntrustedToken::new(token_value)
403        .expect(
404            "UntrustedToken token has to be parseable fro, cookie value in order to get to here",
405        )
406        .deserialize_claims_unchecked::<Claims>()
407        .expect("Claims has to be desirializeable to get to this point")
408        .custom
409}