auth_framework/server/jwt/
jwt_best_practices.rs1use crate::errors::{AuthError, Result};
8use chrono::Utc;
9use jsonwebtoken::{Algorithm, Validation};
10use serde::{Deserialize, Serialize};
11use serde_json::Value;
12use std::collections::{HashMap, HashSet};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum SecurityLevel {
17 Minimum,
19 Recommended,
21 Maximum,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
27pub enum CryptoStrength {
28 Weak,
30 Acceptable,
32 Strong,
34 High,
36}
37
38#[derive(Debug, Clone)]
40pub struct JwtBestPracticesConfig {
41 pub security_level: SecurityLevel,
43
44 pub allowed_algorithms: Vec<Algorithm>,
46
47 pub forbidden_algorithms: Vec<Algorithm>,
49
50 pub max_lifetime: i64,
52
53 pub min_lifetime: i64,
55
56 pub clock_skew: i64,
58
59 pub required_issuers: HashSet<String>,
61
62 pub required_audiences: HashSet<String>,
64
65 pub require_subject: bool,
67
68 pub require_issued_at: bool,
70
71 pub require_expiration: bool,
73
74 pub require_not_before: bool,
76
77 pub require_jwt_id: bool,
79
80 pub max_nested_depth: u8,
82}
83
84impl Default for JwtBestPracticesConfig {
85 fn default() -> Self {
86 Self {
87 security_level: SecurityLevel::Recommended,
88 allowed_algorithms: vec![
89 Algorithm::RS256,
90 Algorithm::RS384,
91 Algorithm::RS512,
92 Algorithm::ES256,
93 Algorithm::ES384,
94 Algorithm::EdDSA,
95 Algorithm::PS256,
96 Algorithm::PS384,
97 Algorithm::PS512,
98 Algorithm::EdDSA,
99 ],
100 forbidden_algorithms: vec![],
101 max_lifetime: 3600, min_lifetime: 60, clock_skew: 30, required_issuers: HashSet::new(),
105 required_audiences: HashSet::new(),
106 require_subject: true,
107 require_issued_at: true,
108 require_expiration: true,
109 require_not_before: false,
110 require_jwt_id: false,
111 max_nested_depth: 1,
112 }
113 }
114}
115
116impl JwtBestPracticesConfig {
117 pub fn minimum_security() -> Self {
119 Self {
120 security_level: SecurityLevel::Minimum,
121 allowed_algorithms: vec![Algorithm::RS256, Algorithm::ES256, Algorithm::PS256],
122 max_lifetime: 86400, require_subject: false,
124 require_issued_at: false,
125 require_jwt_id: false,
126 ..Default::default()
127 }
128 }
129
130 pub fn maximum_security() -> Self {
132 Self {
133 security_level: SecurityLevel::Maximum,
134 allowed_algorithms: vec![
135 Algorithm::ES384,
136 Algorithm::EdDSA,
137 Algorithm::PS384,
138 Algorithm::PS512,
139 Algorithm::EdDSA,
140 ],
141 forbidden_algorithms: vec![Algorithm::HS256, Algorithm::HS384, Algorithm::HS512],
142 max_lifetime: 900, min_lifetime: 30, clock_skew: 5, require_subject: true,
146 require_issued_at: true,
147 require_expiration: true,
148 require_not_before: true,
149 require_jwt_id: true,
150 max_nested_depth: 0, ..Default::default()
152 }
153 }
154}
155
156pub fn get_algorithm_crypto_strength(algorithm: &Algorithm) -> CryptoStrength {
158 match algorithm {
159 Algorithm::HS256 => CryptoStrength::Acceptable,
160 Algorithm::HS384 => CryptoStrength::Strong,
161 Algorithm::HS512 => CryptoStrength::Strong,
162 Algorithm::RS256 => CryptoStrength::Acceptable,
163 Algorithm::RS384 => CryptoStrength::Strong,
164 Algorithm::RS512 => CryptoStrength::Strong,
165 Algorithm::ES256 => CryptoStrength::Strong,
166 Algorithm::ES384 => CryptoStrength::High,
167 Algorithm::EdDSA => CryptoStrength::High,
168 Algorithm::PS256 => CryptoStrength::Strong,
169 Algorithm::PS384 => CryptoStrength::High,
170 Algorithm::PS512 => CryptoStrength::High,
171 }
172}
173
174pub fn is_algorithm_symmetric(algorithm: &Algorithm) -> bool {
175 matches!(
176 algorithm,
177 Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512
178 )
179}
180
181pub fn is_algorithm_asymmetric(algorithm: &Algorithm) -> bool {
182 !is_algorithm_symmetric(algorithm)
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize)]
187pub struct SecureJwtClaims {
188 pub iss: String,
190
191 pub sub: String,
193
194 pub aud: Vec<String>,
196
197 pub exp: i64,
199
200 pub nbf: Option<i64>,
202
203 pub iat: i64,
205
206 pub jti: String,
208
209 #[serde(flatten)]
211 pub custom: HashMap<String, Value>,
212}
213
214pub struct JwtBestPracticesValidator {
216 config: JwtBestPracticesConfig,
217 used_jtis: HashSet<String>, }
219
220impl JwtBestPracticesValidator {
221 pub fn new(config: JwtBestPracticesConfig) -> Self {
223 Self {
224 config,
225 used_jtis: HashSet::new(),
226 }
227 }
228
229 pub fn validate_token_format(&self, token: &str) -> Result<()> {
231 let parts: Vec<&str> = token.split('.').collect();
232 if parts.len() != 3 {
233 return Err(AuthError::InvalidToken("Invalid JWT format".to_string()));
234 }
235
236 if token.len() > 8192 {
238 return Err(AuthError::InvalidToken("Token too large".to_string()));
239 }
240
241 Ok(())
242 }
243
244 pub fn validate_algorithm(&self, algorithm: &Algorithm) -> Result<()> {
246 if self.config.forbidden_algorithms.contains(algorithm) {
248 return Err(AuthError::InvalidToken(format!(
249 "Forbidden algorithm: {:?}",
250 algorithm
251 )));
252 }
253
254 if !self.config.allowed_algorithms.contains(algorithm) {
256 return Err(AuthError::InvalidToken(format!(
257 "Algorithm not allowed: {:?}",
258 algorithm
259 )));
260 }
261
262 let strength = get_algorithm_crypto_strength(algorithm);
264 match self.config.security_level {
265 SecurityLevel::Minimum => {
266 if strength < CryptoStrength::Acceptable {
267 return Err(AuthError::InvalidToken("Algorithm too weak".to_string()));
268 }
269 }
270 SecurityLevel::Recommended => {
271 if strength < CryptoStrength::Strong {
272 return Err(AuthError::InvalidToken(
273 "Algorithm not recommended".to_string(),
274 ));
275 }
276 }
277 SecurityLevel::Maximum => {
278 if strength < CryptoStrength::High {
279 return Err(AuthError::InvalidToken(
280 "Algorithm insufficient for maximum security".to_string(),
281 ));
282 }
283 }
284 }
285
286 Ok(())
287 }
288
289 pub fn validate_standard_claims(&mut self, claims: &SecureJwtClaims) -> Result<()> {
291 let now = Utc::now().timestamp();
292
293 if claims.exp <= now {
295 return Err(AuthError::InvalidToken("Token has expired".to_string()));
296 }
297
298 if let Some(nbf) = claims.nbf
300 && nbf > now + self.config.clock_skew
301 {
302 return Err(AuthError::InvalidToken(
303 "Token is not yet valid".to_string(),
304 ));
305 }
306
307 if claims.iat > now + self.config.clock_skew {
309 return Err(AuthError::InvalidToken(
310 "Token issued in the future".to_string(),
311 ));
312 }
313
314 let lifetime = claims.exp - claims.iat;
316 if lifetime > self.config.max_lifetime {
317 return Err(AuthError::InvalidToken(
318 "Token lifetime too long".to_string(),
319 ));
320 }
321 if lifetime < self.config.min_lifetime {
322 return Err(AuthError::InvalidToken(
323 "Token lifetime too short".to_string(),
324 ));
325 }
326
327 if !self.config.required_issuers.is_empty()
329 && !self.config.required_issuers.contains(&claims.iss)
330 {
331 return Err(AuthError::InvalidToken("Invalid issuer".to_string()));
332 }
333
334 if !self.config.required_audiences.is_empty() {
336 let has_valid_audience = claims
337 .aud
338 .iter()
339 .any(|aud| self.config.required_audiences.contains(aud));
340 if !has_valid_audience {
341 return Err(AuthError::InvalidToken("Invalid audience".to_string()));
342 }
343 }
344
345 if self.config.require_jwt_id {
347 if self.used_jtis.contains(&claims.jti) {
348 return Err(AuthError::InvalidToken("Token replay detected".to_string()));
349 }
350 self.used_jtis.insert(claims.jti.clone());
351 }
352
353 Ok(())
354 }
355
356 pub fn create_validation_rules(&self, algorithm: Algorithm) -> Result<Validation> {
358 let mut validation = Validation::new(algorithm);
359
360 validation.leeway = self.config.clock_skew as u64;
362 validation.validate_exp = self.config.require_expiration;
363 validation.validate_nbf = self.config.require_not_before;
364
365 if !self.config.required_issuers.is_empty() {
367 let issuers: Vec<&str> = self
368 .config
369 .required_issuers
370 .iter()
371 .map(|s| s.as_str())
372 .collect();
373 validation.set_issuer(&issuers);
374 }
375
376 if !self.config.required_audiences.is_empty() {
378 let audiences: Vec<&str> = self
379 .config
380 .required_audiences
381 .iter()
382 .map(|s| s.as_str())
383 .collect();
384 validation.set_audience(&audiences);
385 }
386
387 Ok(validation)
388 }
389
390 pub fn get_security_recommendations(&self) -> Vec<String> {
392 let mut recommendations = Vec::new();
393
394 if self
396 .config
397 .allowed_algorithms
398 .iter()
399 .any(is_algorithm_symmetric)
400 {
401 recommendations.push(
402 "Consider using asymmetric algorithms (RS*, ES*, PS*) for better security"
403 .to_string(),
404 );
405 }
406
407 if self.config.max_lifetime > 3600 {
409 recommendations.push("Consider reducing token lifetime to 1 hour or less".to_string());
410 }
411
412 if !self.config.require_jwt_id {
414 recommendations
415 .push("Consider enabling JWT ID (jti) claim for replay protection".to_string());
416 }
417
418 if !self.config.require_issued_at {
419 recommendations
420 .push("Consider requiring issued at (iat) claim for better validation".to_string());
421 }
422
423 recommendations
424 }
425
426 pub fn clear_used_jtis(&mut self) {
428 self.used_jtis.clear();
429 }
430
431 pub fn get_config(&self) -> &JwtBestPracticesConfig {
433 &self.config
434 }
435}
436
437#[cfg(test)]
438mod tests {
439 use super::*;
440
441 #[test]
442 fn test_algorithm_strength_classification() {
443 assert_eq!(
444 get_algorithm_crypto_strength(&Algorithm::HS256),
445 CryptoStrength::Acceptable
446 );
447 assert_eq!(
448 get_algorithm_crypto_strength(&Algorithm::ES384),
449 CryptoStrength::High
450 );
451 assert_eq!(
452 get_algorithm_crypto_strength(&Algorithm::EdDSA),
453 CryptoStrength::High
454 );
455 }
456
457 #[test]
458 fn test_security_level_configuration() {
459 let min_config = JwtBestPracticesConfig::minimum_security();
460 let max_config = JwtBestPracticesConfig::maximum_security();
461
462 assert_eq!(min_config.security_level, SecurityLevel::Minimum);
463 assert_eq!(max_config.security_level, SecurityLevel::Maximum);
464 assert!(max_config.max_lifetime < min_config.max_lifetime);
465 assert!(max_config.require_jwt_id);
466 assert!(!min_config.require_jwt_id);
467 }
468
469 #[test]
470 fn test_jwt_best_practices_validation() {
471 let config = JwtBestPracticesConfig::default();
472 let validator = JwtBestPracticesValidator::new(config);
473
474 assert!(validator.validate_algorithm(&Algorithm::ES256).is_ok());
476 }
477
478 #[test]
479 fn test_token_format_validation() {
480 let config = JwtBestPracticesConfig::default();
481 let validator = JwtBestPracticesValidator::new(config);
482
483 assert!(
484 validator
485 .validate_token_format("header.payload.signature")
486 .is_ok()
487 );
488 assert!(validator.validate_token_format("invalid.format").is_err());
489 assert!(
490 validator
491 .validate_token_format("too.many.parts.here")
492 .is_err()
493 );
494 }
495}
496
497