actix_jwt_auth_middleware/
authority.rs1use 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#[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#[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 refresh_authorizer: ReAuth,
88 #[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 #[builder(default = "true")]
105 renew_access_token_automatically: bool,
106 #[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 #[builder(default = "false")]
124 renew_refresh_token_automatically: bool,
125 #[builder(default = "false")]
130 enable_header_tokens: bool,
131 #[builder(default = "false")]
136 enable_authorization_header: bool,
137 #[builder(default = "false")]
142 enable_query_tokens: bool,
143 #[builder(default = "true")]
148 enable_cookie_tokens: bool,
149 verifying_key: Algo::VerifyingKey,
153 #[builder(default = "pull_from_token_signer!(self, algorithm)")]
162 algorithm: Algo,
163 #[builder(default = "pull_from_token_signer!(self, time_options)")]
170 time_options: TimeOptions,
171 #[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 #[allow(clippy::new_ret_no_self)]
200 pub fn new() -> AuthorityBuilder<Claims, Algo, ReAuth, Args> {
201 AuthorityBuilder::default()
202 }
203
204 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 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 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}