In modern web development, security is paramount. Rust, known for its safety and performance, offers the Axum framework for building web applications. Implementing robust authentication mechanisms is essential to protect user data and maintain application integrity. This guide provides a step-by-step approach to setting up Axum authentication for secure Rust web apps.
Understanding Axum and Authentication
Axum is a web framework for Rust that emphasizes modularity and ease of use. It allows developers to build scalable and secure web services. Authentication in Axum can be achieved through middleware, which intercepts requests to verify user credentials before granting access to protected resources.
Prerequisites
- Rust installed on your development machine
- Basic knowledge of Rust and Axum framework
- Experience with HTTP and web security concepts
- Dependencies: axum, tower, tokio, serde, serde_json, jsonwebtoken
Setting Up the Project
Create a new Rust project and add dependencies in Cargo.toml:
[package]
name = "axum-auth-example"
version = "0.1.0"
edition = "2021"
[dependencies]
axum = "0.7"
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
jsonwebtoken = "8.1"
tower = "0.4"
Implementing JWT Authentication
JSON Web Tokens (JWT) are a popular method for stateless authentication. We'll generate tokens upon login and verify them for protected routes.
Creating the Token
Define a struct for user claims and a function to create tokens:
use jsonwebtoken::{encode, Header, EncodingKey};
use serde::{Serialize};
#[derive(Serialize)]
struct Claims {
sub: String,
exp: usize,
}
fn create_jwt(user_id: &str) -> String {
let expiration = chrono::Utc::now()
.checked_add_signed(chrono::Duration::hours(24))
.expect("valid timestamp")
.timestamp() as usize;
let claims = Claims {
sub: user_id.to_owned(),
exp: expiration,
};
encode(&Header::default(), &claims, &EncodingKey::from_secret(b"secret"))
.expect("Token creation failed")
}
Verifying the Token
Implement middleware to verify JWTs on protected routes:
use axum::{
async_trait,
extract::{FromRequest, RequestParts},
http::StatusCode,
response::IntoResponse,
middleware::Next,
};
use jsonwebtoken::{decode, Validation, DecodingKey, TokenData};
use serde::Deserialize;
#[derive(Deserialize)]
struct Claims {
sub: String,
exp: usize,
}
struct AuthenticatedUser {
user_id: String,
}
#[async_trait]
impl FromRequest for AuthenticatedUser
where
B: Send,
{
type Rejection = (StatusCode, String);
async fn from_request(req: &mut RequestParts) -> Result {
let auth_header = req.headers()
.and_then(|headers| headers.get("authorization"))
.and_then(|value| value.to_str().ok());
if let Some(header_value) = auth_header {
if header_value.starts_with("Bearer ") {
let token = &header_value[7..];
let token_data: TokenData = decode(
token,
&DecodingKey::from_secret(b"secret"),
&Validation::default(),
).map_err(|_| (StatusCode::UNAUTHORIZED, "Invalid token".to_string()))?;
return Ok(AuthenticatedUser {
user_id: token_data.claims.sub,
});
}
}
Err((StatusCode::UNAUTHORIZED, "Missing or invalid authorization header".to_string()))
}
}
Protecting Routes with Middleware
Apply middleware to routes that require authentication:
use axum::{
routing::get,
Router,
};
async fn protected_route(user: AuthenticatedUser) -> String {
format!("Hello, {}!", user.user_id)
}
fn app() -> Router {
Router::new()
.route("/protected", get(protected_route))
.layer(axum::middleware::from_fn_with_state(
(),
|req, next| {
let fut = AuthenticatedUser::from_request(req);
async move {
match fut.await {
Ok(user) => next.run(req).await,
Err((status, msg))) => {
let response = axum::response::Response::builder()
.status(status)
.body(msg.into())
.unwrap();
response
}
}
}
}
))
}
Testing the Authentication
Start the server and test with curl:
curl -H "Authorization: Bearer your_jwt_token" http://localhost:3000/protected
If the token is valid, you'll receive a personalized greeting. Otherwise, you'll get an unauthorized error.
Conclusion
Implementing JWT authentication in Axum enhances your web application's security by ensuring only authorized users can access sensitive routes. Combining middleware with token verification provides a scalable and stateless authentication solution suitable for modern web services built with Rust.