Authentication and Authorization
Volga provides a flexible set of tools for implementing authentication and authorization in your web applications. It supports both Basic Authentication and Bearer Token (JWT)-based authentication, with built-in facilities for access control based on roles, permissions, or custom logic.
Basic Authentication
Basic authentication is a simple mechanism for verifying user credentials (username and password) via the Authorization: Basic HTTP header.
Dependencies
[dependencies]
volga = { version = "...", features = ["basic-auth"] }
Example
use volga::{
App, HttpResult,
headers::WWW_AUTHENTICATE,
auth::Basic,
status, ok
};
#[tokio::main]
async fn main() -> std::io::Result<()> {
let mut app = App::new();
app.map_get("/protected", protected);
app.run().await
}
async fn protected(auth: Basic) -> HttpResult {
let (expected_user, expected_pass) = get_credentials_from_db().await;
if auth.validate(&expected_user, &expected_pass) {
ok!("Access granted")
} else {
status!(401, "Unauthorized", [
(WWW_AUTHENTICATE, "Basic realm=\"Restricted area\"")
])
}
}
async fn get_credentials_from_db() -> (String, String) {
// In a real application, retrieve this securely
("foo".into(), "bar".into())
}
The Basic struct parses the HTTP header and provides validate() and validate_base64() for credential comparison.
Bearer Token Authentication (JWT)
JWT (JSON Web Tokens) are more robust and support structured claims. This allows you to implement both authentication and fine-grained authorization.
Dependencies
[dependencies]
volga = { version = "...", features = ["jwt-auth-full"] }
Token Generation
use std::{ops::Add, time::{SystemTime, UNIX_EPOCH, Duration}};
use serde::{Serialize, Deserialize};
use volga::{
App, Json, HttpResult,
auth::{Claims, BearerTokenService, EncodingKey},
ok, status, bad_request
};
#[tokio::main]
async fn main() -> std::io::Result<()> {
let secret = std::env::var("JWT_SECRET")
.expect("JWT_SECRET must be set");
let mut app = App::new()
.with_bearer_auth(|auth| {
auth.set_encoding_key(EncodingKey::from_secret(secret.as_bytes()))
});
app.map_post("/generate", generate);
app.run().await
}
async fn generate(payload: Json<Payload>, bts: BearerTokenService) -> HttpResult {
if payload.client_id != "foo" || payload.client_secret != "bar" {
return status!(401, "Invalid credentials");
}
let exp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.add(Duration::from_secs(300))
.as_secs();
let claims = Claims {
sub: "user@email.com".into(),
company: "Awesome Co.".into(),
role: "admin".into(),
exp,
};
let token = bts.encode(&claims)?.to_string();
ok!(AuthData { access_token: token })
}
#[derive(Claims, Serialize, Deserialize)]
struct Claims {
sub: String,
company: String,
role: String,
exp: u64,
}
#[derive(Serialize)]
struct AuthData {
access_token: String,
}
#[derive(Deserialize)]
struct Payload {
client_id: String,
client_secret: String,
}
Bearer Auth Configuration
The BearerAuthConfig enforces the following security policies by default:
require_httpsis enabled — non-TLS, non-loopback requests are rejected with400 Bad Request. Disable for reverse-proxy deployments where TLS is terminated upstream.strip_token_from_requestis enabled — theAuthorizationheader is removed after successful validation. Disable if downstream handlers need access to the token.
let mut app = App::new()
.with_bearer_auth(|auth| auth
.set_decoding_key(DecodingKey::from_secret(secret.as_bytes()))
.require_https(false) // disable for reverse-proxy deployments
.strip_token_from_request(false) // keep Authorization header for downstream handlers
);
When configuring audience validation via with_aud(...), the aud claim is automatically added to required claims — tokens missing it are rejected. Call without_strict_aud() to allow tokens that do not include aud:
let mut app = App::new()
.with_bearer_auth(|auth| auth
.set_decoding_key(DecodingKey::from_secret(secret.as_bytes()))
.with_aud(["my-service"])
.without_strict_aud()
);
Info
EncodingKey, DecodingKey, and Algorithm are now native Volga types (no longer re-exported from jsonwebtoken). Import paths remain the same (volga::auth::{EncodingKey, DecodingKey}), but jsonwebtoken::ErrorKind is no longer available — use the PEM / base64 / secret / env / file constructors provided by Volga instead. Token validation settings live on BearerAuthConfig; the previous BearerTokenService::validation() accessor has been removed.
JWT Usage
The authorize() middleware provides tools to implement roles-based or permissions-based access control and can be difined for a single route, group of routes or the entire application.
use serde::Deserialize;
use volga::{
App, ok,
auth::{Claims, DecodingKey, roles},
};
#[tokio::main]
async fn main() -> std::io::Result<()> {
let secret = std::env::var("JWT_SECRET")
.expect("JWT_SECRET must be set");
let mut app = App::new()
.with_bearer_auth(|auth| {
auth.set_decoding_key(DecodingKey::from_secret(secret.as_bytes()))
});
app.map_get("/me", me)
.authorize::<Claims>(roles(["admin", "user"]));
app.run().await
}
async fn me() -> &'static str {
"Hello from protected route"
}
#[derive(Claims, Deserialize)]
struct Claims {
sub: String,
company: String,
role: String,
exp: u64,
}
Warning
To ensure proper JWT functionality, you must use the same JWT_SECRET both when generating the token and when validating it on protected routes. This secret is used to sign the token during generation and to verify the signature during validation. If these values differ - the token will be rejected as invalid.
Defining Claims
The jwt-auth-full feature enables the Claims derive macro for defining JWT claims. Alternatively, you can define claims using:
claims! macro
use volga::auth::claims;
claims! {
#[derive(Deserialize)]
struct Claims {
sub: String,
role: String,
permissions: Vec<String>,
}
}
Manual Implementation
use volga::auth::AuthClaims;
#[derive(Deserialize)]
struct Claims {
sub: String,
role: String,
permissions: Vec<String>,
}
impl AuthClaims for Claims {
fn role(&self) -> Option<&str> {
Some(&self.role)
}
fn permissions(&self) -> Option<&[String]> {
Some(&self.permissions)
}
}
Declarative Access Control with Authorizer
Volga provides a powerful Authorizer system that lets you define access rules declaratively.
Built-in Authorizers
role("admin"): single-role check.roles(["admin", "user"]): multi-role check.permission("write"): single permission.permissions(["read", "write"]): multiple permissions.predicate(|claims| ...): custom logic.
Example
use serde::Deserialize;
use volga::auth::{
AuthClaims, Authorizer,
role, roles, permission, permissions, predicate
};
#[derive(Deserialize)]
struct MyClaims {
role: String,
permissions: Vec<String>,
}
impl AuthClaims for MyClaims {
fn role(&self) -> Option<&str> {
Some(&self.role)
}
fn permissions(&self) -> Option<&[String]> {
Some(&self.permissions)
}
}
fn main() {
let admin = role("admin");
let editors = roles(["editor", "contributor"]);
let can_write = permission("write");
let access_policy = admin.or(editors).and(can_write);
let user = MyClaims {
role: "editor".into(),
permissions: vec!["write".into()],
};
assert!(access_policy.validate(&user));
}