1. Password Hashing with Argon2
use argon2::{Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
use argon2::password_hash::rand_core::OsRng;
use argon2::password_hash::SaltString;
fn hash_password(password: &str) -> Result<String, argon2::password_hash::Error> {
let salt = SaltString::generate(&mut OsRng);
let argon2 = Argon2::default();
let hash = argon2.hash_password(password.as_bytes(), &salt)?;
Ok(hash.to_string())
}
fn verify_password(password: &str, hash: &str) -> bool {
let parsed_hash = PasswordHash::new(hash).unwrap();
Argon2::default().verify_password(password.as_bytes(), &parsed_hash).is_ok()
}
2. JWT Token
use jsonwebtoken::{encode, decode, Header, Validation, EncodingKey, DecodingKey};
use serde::{Deserialize, Serialize};
use chrono::{Utc, Duration};
#[derive(Debug, Serialize, Deserialize)]
struct Claims {
sub: String, // user id
role: String,
exp: usize, // expiration
iat: usize, // issued at
}
struct JwtService {
secret: String,
}
impl JwtService {
fn create_token(&self, user_id: &str, role: &str) -> Result<String, jsonwebtoken::errors::Error> {
let claims = Claims {
sub: user_id.to_string(),
role: role.to_string(),
exp: (Utc::now() + Duration::hours(24)).timestamp() as usize,
iat: Utc::now().timestamp() as usize,
};
encode(&Header::default(), &claims, &EncodingKey::from_secret(self.secret.as_bytes()))
}
fn verify_token(&self, token: &str) -> Result<Claims, jsonwebtoken::errors::Error> {
let data = decode::<Claims>(
token,
&DecodingKey::from_secret(self.secret.as_bytes()),
&Validation::default(),
)?;
Ok(data.claims)
}
}
3. Auth Middleware with Axum
use axum::{extract::FromRequestParts, http::request::Parts};
struct AuthUser {
user_id: String,
role: String,
}
#[axum::async_trait]
impl<S> FromRequestParts<S> for AuthUser
where
S: Send + Sync,
{
type Rejection = AppError;
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
let auth_header = parts.headers
.get("Authorization")
.and_then(|v| v.to_str().ok())
.ok_or(AppError::Unauthorized)?;
let token = auth_header.strip_prefix("Bearer ")
.ok_or(AppError::Unauthorized)?;
let claims = jwt_service.verify_token(token)
.map_err(|_| AppError::Unauthorized)?;
Ok(AuthUser {
user_id: claims.sub,
role: claims.role,
})
}
}
// Handler — tự động extract auth
async fn get_profile(user: AuthUser) -> Json<UserProfile> {
// user.user_id đã được verify
Json(UserProfile { id: user.user_id })
}
// Role-based guard
async fn admin_only(user: AuthUser) -> Result<Json<String>, AppError> {
if user.role != "admin" {
return Err(AppError::Forbidden);
}
Ok(Json("Admin content".into()))
}
4. Login Flow
#[derive(Deserialize)]
struct LoginRequest {
email: String,
password: String,
}
#[derive(Serialize)]
struct LoginResponse {
access_token: String,
token_type: String,
expires_in: u64,
}
async fn login(
State(state): State<AppState>,
Json(input): Json<LoginRequest>,
) -> Result<Json<LoginResponse>, AppError> {
let user = state.user_repo.find_by_email(&input.email).await?
.ok_or(AppError::Unauthorized)?;
if !verify_password(&input.password, &user.password_hash) {
return Err(AppError::Unauthorized);
}
let token = state.jwt.create_token(&user.id.to_string(), &user.role)?;
Ok(Json(LoginResponse {
access_token: token,
token_type: "Bearer".into(),
expires_in: 86400,
}))
}
Next article: WebSockets & Real-time.