ej_auth/
jwt.rs

1//! JWT token management for the EJ authentication system.
2//!
3//! This module provides functions for creating and validating JSON Web Tokens (JWT)
4//! used throughout the EJ framework for stateless authentication. It handles token
5//! signing, verification, and claim extraction with secure defaults.
6//!
7//! # Usage
8//!
9//! The module provides two main functions for JWT operations:
10//! - [`jwt_encode`]: Create signed JWT tokens from claim data
11//! - [`jwt_decode`]: Validate and extract claims from JWT tokens
12//!
13//! # Examples
14//!
15//! ```rust
16//! use ej_auth::jwt::{jwt_encode, jwt_decode};
17//! use serde::{Serialize, Deserialize};
18//! use std::env;
19//! unsafe { env::set_var("JWT_SECRET", "MySuperSecret"); }
20//!
21//! #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
22//! struct UserClaims {
23//!     user_id: String,
24//!     role: String,
25//!     exp: usize,
26//! }
27//!
28//! // Create a token
29//! let claims = UserClaims {
30//!     user_id: "admin".to_string(),
31//!     role: "administrator".to_string(),
32//!     exp: 4118335200,
33//! };
34//!
35//! let token = jwt_encode(&claims).unwrap();
36//!
37//! // Validate and decode the token
38//! let decoded = jwt_decode::<UserClaims>(&token).unwrap();
39//! assert_eq!(claims, decoded.claims);
40//! ```
41
42use crate::prelude::*;
43use std::sync::LazyLock;
44
45use jsonwebtoken::{
46    Algorithm, DecodingKey, EncodingKey, Header, TokenData, Validation, decode, encode,
47};
48use serde::{Serialize, de::DeserializeOwned};
49
50/// Lazily initialized cryptographic keys for JWT operations.
51///
52/// Keys are loaded once from the JWT_SECRET environment variable and reused
53/// for all token operations. This provides better performance than recreating
54/// keys for each operation while maintaining security.
55static KEYS: LazyLock<Keys> = LazyLock::new(|| {
56    let secret = std::env::var("JWT_SECRET").expect("JWT_SECRET must be set");
57    Keys::new(secret.as_bytes())
58});
59
60/// JWT signing algorithm used throughout the EJ framework.
61static ALGORITHM: LazyLock<Algorithm> = LazyLock::new(|| Algorithm::HS256);
62
63/// Cryptographic key pair for JWT signing and verification.
64struct Keys {
65    /// Key used for signing new JWT tokens.
66    encoding: EncodingKey,
67    /// Key used for verifying existing JWT tokens.
68    decoding: DecodingKey,
69}
70
71impl Keys {
72    /// Creates a new key pair from the provided secret.
73    ///
74    /// # Arguments
75    ///
76    /// * `secret` - Raw bytes of the signing secret
77    fn new(secret: &[u8]) -> Self {
78        Self {
79            encoding: EncodingKey::from_secret(secret),
80            decoding: DecodingKey::from_secret(secret),
81        }
82    }
83}
84
85/// Creates a signed JWT token from the provided claims.
86///
87/// This function serializes the claims data and creates a signed JWT token
88/// using the configured signing algorithm and secret. The resulting token
89/// can be used for authentication across EJ services.
90///
91/// # Arguments
92///
93/// * `body` - Claims data to encode in the token (must be serializable)
94///
95/// # Returns
96///
97/// * `Ok(String)` - Base64-encoded JWT token
98/// * `Err(Error)` - Token creation or serialization errors
99///
100/// # Security Notes
101///
102/// - Claims are not encrypted, only signed for integrity
103/// - Include expiration claims to prevent token replay attacks
104/// - Keep payload minimal to reduce token size and attack surface
105///
106/// # Example
107///
108/// ```rust
109/// use ej_auth::jwt::{jwt_encode, jwt_decode};
110/// use serde::{Serialize, Deserialize};
111/// use std::env;
112/// unsafe { env::set_var("JWT_SECRET", "MySuperSecret"); }
113///
114/// #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
115/// struct BuilderClaims {
116///     builder_id: String,
117///     exp: usize,
118/// }
119///
120/// let claims = BuilderClaims {
121///     builder_id: "builder-001".to_string(),
122///     exp: 4118335200,
123/// };
124///
125/// let token = jwt_encode(&claims).unwrap();
126/// let token_data = jwt_decode::<BuilderClaims>(&token).unwrap();
127/// assert_eq!(claims, token_data.claims);
128/// ```
129pub fn jwt_encode<T>(body: &T) -> Result<String>
130where
131    T: Serialize,
132{
133    let header = Header::new(*ALGORITHM);
134    Ok(encode(&header, body, &KEYS.encoding)?)
135}
136
137/// Validates and decodes a JWT token to extract claims.
138///
139/// This function verifies the token signature, validates the structure,
140/// and deserializes the claims data. Only tokens signed with the correct
141/// secret and matching algorithm will be accepted.
142///
143/// # Arguments
144///
145/// * `token` - JWT token string to validate and decode
146///
147/// # Returns
148///
149/// * `Ok(TokenData<T>)` - Validated token with extracted claims
150/// * `Err(Error)` - Invalid token, signature mismatch, or deserialization errors
151///
152/// # Validation
153///
154/// The function performs these validation steps:
155/// - Signature verification using the configured secret
156/// - Algorithm validation (must match HS256)
157/// - Token structure validation
158/// - Claims deserialization
159///
160/// See `jwt_encode` for a code example
161/// ```
162pub fn jwt_decode<T>(token: &str) -> Result<TokenData<T>>
163where
164    T: DeserializeOwned,
165{
166    Ok(decode(token, &KEYS.decoding, &Validation::new(*ALGORITHM))?)
167}