use jsonwebtoken::{encode, decode, Header, EncodingKey, DecodingKey, Validation, Algorithm};
#[derive(Debug, Serialize, Deserialize)]
struct Claims {
sub: String,
exp: usize,
iat: usize,
iss: String,
}
pub struct JwtService {
encoding_key: EncodingKey,
decoding_key: DecodingKey,
validation: Validation,
}
impl JwtService {
pub fn new(secret: &[u8], issuer: &str) -> Self {
let mut validation = Validation::new(Algorithm::HS256);
validation.set_issuer(&[issuer]);
validation.leeway = 60;
Self {
encoding_key: EncodingKey::from_secret(secret),
decoding_key: DecodingKey::from_secret(secret),
validation,
}
}
pub fn create_token(&self, claims: &Claims) -> Result<String, AuthError> {
encode(&Header::default(), claims, &self.encoding_key)
.map_err(|_| AuthError::TokenCreation)
}
pub fn validate_token(&self, token: &str) -> Result<Claims, AuthError> {
decode::<Claims>(token, &self.decoding_key, &self.validation)
.map(|data| data.claims)
.map_err(|err| match *err.kind() {
ErrorKind::ExpiredSignature => AuthError::TokenExpired,
_ => AuthError::InvalidToken,
})
}
}
use argon2::{
password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
Argon2,
};
fn hash_password(password: &str) -> Result<String, argon2::password_hash::Error> {
let salt = SaltString::generate(&mut OsRng);
let argon2 = Argon2::default();
Ok(argon2.hash_password(password.as_bytes(), &salt)?.to_string())
}
fn verify_password(password: &str, hash: &str) -> bool {
let parsed_hash = match PasswordHash::new(hash) {
Ok(h) => h,
Err(_) => return false,
};
Argon2::default().verify_password(password.as_bytes(), &parsed_hash).is_ok()
}
pub async fn auth_middleware(
Extension(jwt_service): Extension<Arc<JwtService>>,
mut req: Request,
next: Next,
) -> Result<Response, StatusCode> {
let auth_header = req.headers()
.get(header::AUTHORIZATION)
.and_then(|h| h.to_str().ok())
.and_then(|h| h.strip_prefix("Bearer "));
let token = auth_header.ok_or(StatusCode::UNAUTHORIZED)?;
let claims = jwt_service.validate_token(token).map_err(|_| StatusCode::UNAUTHORIZED)?;
req.extensions_mut().insert(AuthenticatedUser::from(claims));
Ok(next.run(req).await)
}
1---2name: rust-backend-auth3description: Implements authentication and authorization for Rust backend services using JWT, password hashing, sessions, and middleware patterns. Use when building auth systems, implementing login/logout, protecting routes, hashing passwords, or working with JWT tokens in Axum/Tower applications.4---5
6<objective>
7Provides production-ready authentication patterns for Rust backend services. Covers JWT token creation/validation, secure password hashing with Argon2, and auth middleware that injects user context.
8</objective>
9
10<essential_principles>
111. **Password Storage**: Use Argon2id with random salts. Store the PHC string.
122. **Token Validation**: Always validate exp, iss, aud claims. Handle clock skew.
133. **Middleware Design**: Inject authenticated user via request extensions. Fail closed.
14</essential_principles>
15
16<patterns>
17<pattern name="jwt_service">
18**JWT Encoding and Decoding**
19
20```rust
21use jsonwebtoken::{encode, decode, Header, EncodingKey, DecodingKey, Validation, Algorithm};
22
23#[derive(Debug, Serialize, Deserialize)]
24struct Claims {
25 sub: String,
26 exp: usize,
27 iat: usize,
28 iss: String,
29}
30
31pub struct JwtService {
32 encoding_key: EncodingKey,
33 decoding_key: DecodingKey,
34 validation: Validation,
35}
36
37impl JwtService {
38 pub fn new(secret: &[u8], issuer: &str) -> Self {
39 let mut validation = Validation::new(Algorithm::HS256);
40 validation.set_issuer(&[issuer]);
41 validation.leeway = 60;
42
43 Self {
44 encoding_key: EncodingKey::from_secret(secret),
45 decoding_key: DecodingKey::from_secret(secret),
46 validation,
47 }
48 }
49
50 pub fn create_token(&self, claims: &Claims) -> Result<String, AuthError> {
51 encode(&Header::default(), claims, &self.encoding_key)
52 .map_err(|_| AuthError::TokenCreation)
53 }
54
55 pub fn validate_token(&self, token: &str) -> Result<Claims, AuthError> {
56 decode::<Claims>(token, &self.decoding_key, &self.validation)
57 .map(|data| data.claims)
58 .map_err(|err| match *err.kind() {
59 ErrorKind::ExpiredSignature => AuthError::TokenExpired,
60 _ => AuthError::InvalidToken,
61 })
62 }
63}
64```
65</pattern>
66
67<pattern name="password_hashing">
68**Argon2 Password Hashing**
69
70```rust
71use argon2::{
72 password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
73 Argon2,
74};
75
76fn hash_password(password: &str) -> Result<String, argon2::password_hash::Error> {
77 let salt = SaltString::generate(&mut OsRng);
78 let argon2 = Argon2::default();
79 Ok(argon2.hash_password(password.as_bytes(), &salt)?.to_string())
80}
81
82fn verify_password(password: &str, hash: &str) -> bool {
83 let parsed_hash = match PasswordHash::new(hash) {
84 Ok(h) => h,
85 Err(_) => return false,
86 };
87 Argon2::default().verify_password(password.as_bytes(), &parsed_hash).is_ok()
88}
89```
90</pattern>
91
92<pattern name="auth_middleware">
93**Auth Middleware**
94
95```rust
96pub async fn auth_middleware(
97 Extension(jwt_service): Extension<Arc<JwtService>>,
98 mut req: Request,
99 next: Next,
100) -> Result<Response, StatusCode> {
101 let auth_header = req.headers()
102 .get(header::AUTHORIZATION)
103 .and_then(|h| h.to_str().ok())
104 .and_then(|h| h.strip_prefix("Bearer "));
105
106 let token = auth_header.ok_or(StatusCode::UNAUTHORIZED)?;
107 let claims = jwt_service.validate_token(token).map_err(|_| StatusCode::UNAUTHORIZED)?;
108
109 req.extensions_mut().insert(AuthenticatedUser::from(claims));
110 Ok(next.run(req).await)
111}
112```
113</pattern>
114</patterns>
115
116<security_checklist>
117- [ ] Secrets from environment variables, never hardcoded
118- [ ] Algorithm explicitly specified in Validation
119- [ ] exp, iss, aud validated on every decode
120- [ ] Argon2id for password hashing
121- [ ] Generic error messages ("Invalid credentials")
122- [ ] Rate limiting on auth endpoints
123</security_checklist>
124
125<success_criteria>
126- [ ] Passwords hashed with Argon2id
127- [ ] JWT tokens include sub, exp, iat, iss claims
128- [ ] Auth middleware injects user into extensions
129- [ ] Protected routes return 401 without valid token
130- [ ] All secrets from environment variables
131</success_criteria>