| name | salvo-auth |
| description | Implement authentication and authorization using JWT, Basic Auth, or custom schemes. Use for securing API endpoints and user management. |
| version | 0.89.3 |
| tags | ["security","authentication","jwt","basic-auth"] |
Salvo Authentication
This skill helps implement authentication and authorization in Salvo applications.
JWT Authentication
Setup
[dependencies]
salvo = { version = "0.89.3", features = ["jwt-auth"] }
jsonwebtoken = "9"
serde = { version = "1", features = ["derive"] }
chrono = "0.4"
JWT Middleware Setup
use salvo::jwt_auth::{JwtAuth, JwtClaims};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct JwtClaims {
sub: String,
exp: i64,
role: String,
}
const SECRET_KEY: &str = "your-secret-key-at-least-32-bytes";
#[tokio::main]
async fn main() {
let auth_handler = JwtAuth::new("secret_key")
.finders(vec![
Box::new(HeaderFinder::new()),
Box::new(QueryFinder::new("token")),
]);
let router = Router::new()
.push(Router::with_path("login").post(login))
.push(
Router::with_path("protected")
.hoop(auth_handler)
.get(protected_handler)
);
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}
Login Handler
use jsonwebtoken::{encode, EncodingKey, Header};
use salvo::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct LoginRequest {
username: String,
password: String,
}
#[derive(Serialize)]
struct LoginResponse {
token: String,
}
#[handler]
async fn login(body: JsonBody<LoginRequest>) -> Result<Json<LoginResponse>, StatusError> {
let req = body.into_inner();
if req.username != "admin" || req.password != "password" {
return Err(StatusError::unauthorized());
}
let claims = JwtClaims {
sub: req.username,
exp: (chrono::Utc::now() + chrono::Duration::hours(24)).timestamp(),
role: "user".to_string(),
};
let token = encode(
&Header::default(),
&claims,
&EncodingKey::from_secret(SECRET_KEY.as_bytes()),
)
.map_err(|_| StatusError::internal_server_error())?;
Ok(Json(LoginResponse { token }))
}
Protected Handler
use salvo::jwt_auth::JwtAuthDepotExt;
#[handler]
async fn protected_handler(depot: &mut Depot) -> Result<String, StatusError> {
let token_data = depot.jwt_auth_data::<JwtClaims>()
.ok_or_else(|| StatusError::unauthorized())?;
Ok(format!("Hello, {}! Role: {}", token_data.claims.sub, token_data.claims.role))
}
Basic Authentication
Setup
[dependencies]
salvo = { version = "0.89.3", features = ["basic-auth"] }
Basic Auth Middleware
use salvo::prelude::*;
use salvo::basic_auth::{BasicAuth, BasicAuthValidator};
struct MyValidator;
impl BasicAuthValidator for MyValidator {
async fn validate(&self, username: &str, password: &str, depot: &mut Depot) -> bool {
if username == "admin" && password == "password" {
depot.insert("user_role", "admin");
true
} else if username == "user" && password == "userpass" {
depot.insert("user_role", "user");
true
} else {
false
}
}
}
#[tokio::main]
async fn main() {
let auth_handler = BasicAuth::new(MyValidator);
let router = Router::new()
.push(
Router::with_path("admin")
.hoop(auth_handler)
.get(admin_handler)
);
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}
Custom Authentication Middleware
use salvo::prelude::*;
#[handler]
async fn auth_middleware(req: &mut Request, depot: &mut Depot, res: &mut Response, ctrl: &mut FlowCtrl) {
let token = req.header::<String>("Authorization")
.and_then(|h| h.strip_prefix("Bearer ").map(String::from));
match token {
Some(token) => {
match validate_token(&token) {
Ok(user_id) => {
depot.insert("user_id", user_id);
ctrl.call_next(req, depot, res).await;
}
Err(_) => {
res.status_code(StatusCode::UNAUTHORIZED);
res.render(Json(serde_json::json!({"error": "Invalid token"})));
ctrl.skip_rest();
}
}
}
None => {
res.status_code(StatusCode::UNAUTHORIZED);
res.render(Json(serde_json::json!({"error": "Missing token"})));
ctrl.skip_rest();
}
}
}
API Key Authentication
#[handler]
async fn api_key_auth(req: &mut Request, depot: &mut Depot, res: &mut Response, ctrl: &mut FlowCtrl) {
let api_key = req.header::<String>("X-API-Key");
match api_key {
Some(key) if is_valid_api_key(&key) => {
depot.insert("api_key", key);
ctrl.call_next(req, depot, res).await;
}
_ => {
res.status_code(StatusCode::UNAUTHORIZED);
res.render("Invalid API key");
ctrl.skip_rest();
}
}
}
Session-Based Authentication
use salvo::prelude::*;
use salvo::session::{SessionHandler, CookieStore, SessionDepotExt};
#[tokio::main]
async fn main() {
let session_handler = SessionHandler::builder(
CookieStore::new(),
b"secret_key_must_be_at_least_64_bytes_long_for_security_reasons!!",
)
.build()
.unwrap();
let router = Router::new()
.hoop(session_handler)
.push(Router::with_path("login").post(login))
.push(Router::with_path("profile").get(profile));
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}
#[handler]
async fn login(depot: &mut Depot) -> StatusCode {
let session = depot.session_mut().unwrap();
session.insert("user_id", 123).unwrap();
StatusCode::OK
}
#[handler]
async fn profile(depot: &mut Depot) -> Result<String, StatusError> {
let session = depot.session().unwrap();
let user_id: Option<i64> = session.get("user_id");
match user_id {
Some(id) => Ok(format!("User ID: {}", id)),
None => Err(StatusError::unauthorized()),
}
}
Role-Based Access Control (RBAC)
#[derive(Clone)]
enum Role {
Admin,
User,
Guest,
}
fn require_role(required: Role) -> impl Handler {
move |req: &mut Request, depot: &mut Depot, res: &mut Response, ctrl: &mut FlowCtrl| async move {
let user_role = depot.get::<Role>("user_role");
match user_role {
Some(role) if matches!((role, &required), (Role::Admin, _) | (Role::User, Role::User)) => {
ctrl.call_next(req, depot, res).await;
}
_ => {
res.status_code(StatusCode::FORBIDDEN);
res.render("Insufficient permissions");
ctrl.skip_rest();
}
}
}
}
let router = Router::new()
.push(
Router::with_path("admin")
.hoop(auth_middleware)
.hoop(require_role(Role::Admin))
.get(admin_handler)
);
Best Practices
- Use HTTPS in production
- Store secrets securely (environment variables)
- Use short token expiration times
- Implement refresh token rotation
- Hash passwords with bcrypt/argon2
- Rate limit authentication endpoints
- Log authentication attempts
- Validate all tokens on every request
Related Skills
- salvo-session: Session-based authentication
- salvo-cors: CORS for authenticated APIs
- salvo-rate-limiter: Protect login endpoints