Auth and Middleware (Minimal Patterns) β
This page shows minimal auth and middleware patterns in Vix.cpp.
Rule of this doc:
- one concept
- one minimal
main() - a quick curl test
1) API key middleware (protect one route) β
A public route plus a secure route that requires x-api-key.
cpp
#include <vix.hpp>
#include <vix/middleware/app/presets.hpp>
using namespace vix;
int main()
{
App app;
app.get("/public", [](Request&, Response& res){
res.json({ "ok", true, "scope", "public" });
});
// Install API key middleware only on this prefix
middleware::app::install(app, "/secure/", middleware::app::api_key_dev("dev_key_123"));
app.get("/secure/whoami", [](Request&, Response& res){
res.json({ "ok", true, "scope", "secure", "message", "API key accepted" });
});
app.run(8080);
return 0;
}Try:
bash
curl -i http://127.0.0.1:8080/public
curl -i http://127.0.0.1:8080/secure/whoami
curl -i -H "x-api-key: dev_key_123" http://127.0.0.1:8080/secure/whoami2) Prefix protection (protect all /api routes) β
Everything under /api/ is protected.
cpp
#include <vix.hpp>
#include <vix/middleware/app/presets.hpp>
using namespace vix;
int main()
{
App app;
middleware::app::install(app, "/api/", middleware::app::api_key_dev("dev_key_123"));
app.get("/api/ping", [](Request&, Response& res){
res.json({ "ok", true, "pong", true });
});
app.get("/api/users", [](Request&, Response& res){
res.json({ "ok", true, "data", json::array({ "u1", "u2" }) });
});
app.run(8080);
return 0;
}Try:
bash
curl -i http://127.0.0.1:8080/api/ping
curl -i -H "x-api-key: dev_key_123" http://127.0.0.1:8080/api/ping3) Custom middleware (context style + RequestState) β
Store data into request state and read it in the handler.
cpp
#include <vix.hpp>
#include <vix/middleware/app/adapter.hpp>
#include <chrono>
#include <string>
using namespace vix;
struct RequestId { std::string value; };
static long long now_ms()
{
using namespace std::chrono;
return (long long)time_point_cast<milliseconds>(system_clock::now())
.time_since_epoch().count();
}
static vix::middleware::MiddlewareFn mw_request_id()
{
return [](vix::middleware::Context& ctx, vix::middleware::Next next)
{
RequestId rid;
rid.value = std::to_string(now_ms());
ctx.req().emplace_state<RequestId>(rid);
ctx.res().header("x-request-id", rid.value);
next();
};
}
int main()
{
App app;
app.use(vix::middleware::app::adapt_ctx(mw_request_id()));
app.get("/who", [](Request& req, Response& res){
res.json({ "ok", true, "request_id", req.state<RequestId>().value });
});
app.run(8080);
return 0;
}Try:
bash
curl -i http://127.0.0.1:8080/who4) Role gating (fake auth + admin only) β
Minimal RBAC style gate using headers for the demo.
cpp
#include <vix.hpp>
#include <vix/middleware/app/adapter.hpp>
#include <string>
using namespace vix;
namespace J = vix::json;
struct AuthInfo
{
bool authed{false};
std::string subject;
std::string role;
};
static vix::middleware::MiddlewareFn mw_fake_auth()
{
return [](vix::middleware::Context& ctx, vix::middleware::Next next)
{
AuthInfo a;
const std::string user = ctx.req().header("x-user");
const std::string role = ctx.req().header("x-role");
if (!user.empty())
{
a.authed = true;
a.subject = user;
a.role = role.empty() ? "user" : role;
}
ctx.req().emplace_state<AuthInfo>(a);
next();
};
}
static vix::middleware::MiddlewareFn mw_require_admin()
{
return [](vix::middleware::Context& ctx, vix::middleware::Next next)
{
if (!ctx.req().has_state_type<AuthInfo>() || !ctx.req().state<AuthInfo>().authed)
{
ctx.res().status(401).json(J::obj({ "ok", false, "error", "unauthorized" }));
return;
}
if (ctx.req().state<AuthInfo>().role != "admin")
{
ctx.res().status(403).json(J::obj({ "ok", false, "error", "forbidden", "hint", "admin required" }));
return;
}
next();
};
}
int main()
{
App app;
app.use(vix::middleware::app::adapt_ctx(mw_fake_auth()));
vix::middleware::app::install(app, "/admin/", vix::middleware::app::adapt_ctx(mw_require_admin()));
app.get("/admin/stats", [](Request& req, Response& res)
{
const auto& a = req.state<AuthInfo>();
res.json({ "ok", true, "admin", true, "subject", a.subject });
});
app.run(8080);
return 0;
}Try:
bash
curl -i http://127.0.0.1:8080/admin/stats
curl -i -H "x-user: gaspard" http://127.0.0.1:8080/admin/stats
curl -i -H "x-user: gaspard" -H "x-role: admin" http://127.0.0.1:8080/admin/stats5) Legacy HttpMiddleware style (adapt) β
If you have an older middleware signature (Request, Response, next) you can adapt it.
cpp
#include <vix.hpp>
#include <vix/middleware/app/adapter.hpp>
#include <string>
using namespace vix;
namespace J = vix::json;
static vix::middleware::HttpMiddleware require_header(std::string header, std::string expected)
{
return [header = std::move(header), expected = std::move(expected)](Request& req, Response& res, vix::middleware::Next next)
{
const std::string got = req.header(header);
if (got != expected)
{
res.status(401).json(J::obj({
"ok", false,
"error", "unauthorized",
"required_header", header
}));
return;
}
next();
};
}
int main()
{
App app;
vix::middleware::app::install_exact(
app,
"/api/ping",
vix::middleware::app::adapt(require_header("x-demo", "1"))
);
app.get("/api/ping", [](Request&, Response& res){
res.json({ "ok", true, "pong", true });
});
app.run(8080);
return 0;
}Try:
bash
curl -i http://127.0.0.1:8080/api/ping
curl -i -H "x-demo: 1" http://127.0.0.1:8080/api/ping6) Chaining middleware β
Apply multiple middlewares on the same prefix.
cpp
#include <vix.hpp>
#include <vix/middleware/app/adapter.hpp>
#include <vix/middleware/app/presets.hpp>
using namespace vix;
static vix::middleware::MiddlewareFn mw_mark()
{
return [](vix::middleware::Context& ctx, vix::middleware::Next next)
{
ctx.res().header("x-mw", "on");
next();
};
}
int main()
{
App app;
vix::middleware::app::install(
app,
"/secure/",
vix::middleware::app::chain(
vix::middleware::app::api_key_dev("dev_key_123"),
vix::middleware::app::adapt_ctx(mw_mark())
)
);
app.get("/secure/hello", [](Request&, Response& res){
res.json({ "ok", true, "message", "Hello secure" });
});
app.run(8080);
return 0;
}Try:
bash
curl -i -H "x-api-key: dev_key_123" http://127.0.0.1:8080/secure/helloWhat this teaches β
- Prefix install: protect a group of routes
- Exact install: protect one route
- Context middleware: state and headers
- Legacy middleware adaptation
- Basic RBAC style gating
- Middleware chaining
RBAC (Roles + Permissions) using JWT β
What is RBAC β
RBAC means Role Based Access Control.
You check:
- roles (admin, user, editor)
- permissions (products:write, orders:read)
In Vix.cpp:
- JWT extracts claims
- RBAC builds
Authz require_role()andrequire_perm()enforce rules
Request flow β
- Client sends JWT:
Authorization: Bearer <token> - JWT middleware validates signature
- RBAC builds an
Authzcontext - Role and permission middlewares run
- Handler executes
Minimal RBAC pattern β
cpp
App app;
vix::middleware::auth::JwtOptions jwt_opt{};
jwt_opt.secret = "dev_secret";
jwt_opt.verify_exp = false;
vix::middleware::auth::RbacOptions rbac_opt{};
rbac_opt.require_auth = true;
rbac_opt.use_resolver = false;
auto jwt_mw = vix::middleware::app::adapt_ctx(vix::middleware::auth::jwt(jwt_opt));
auto ctx_mw = vix::middleware::app::adapt_ctx(vix::middleware::auth::rbac_context(rbac_opt));
auto role_mw = vix::middleware::app::adapt_ctx(vix::middleware::auth::require_role("admin"));
auto perm_mw = vix::middleware::app::adapt_ctx(vix::middleware::auth::require_perm("products:write"));
app.use(vix::middleware::app::when(
[](const Request& r){ return r.path() == "/admin"; }, std::move(jwt_mw)));
app.use(vix::middleware::app::when(
[](const Request& r){ return r.path() == "/admin"; }, std::move(ctx_mw)));
app.use(vix::middleware::app::when(
[](const Request& r){ return r.path() == "/admin"; }, std::move(role_mw)));
app.use(vix::middleware::app::when(
[](const Request& r){ return r.path() == "/admin"; }, std::move(perm_mw)));Reading Authz in a handler β
cpp
app.get("/admin", [](Request& req, Response& res){
auto& authz = req.state<vix::middleware::auth::Authz>();
res.json({
"ok", true,
"sub", authz.subject,
"has_admin", authz.has_role("admin"),
"has_products_write", authz.has_perm("products:write")
});
});Common statuses β
- 401: missing token, invalid token, invalid signature
- 403: authenticated but missing required role or permission
Rate limiting (minimal) β
Rate limiting protects your API from brute force, spam, and bursts.
The model is a token bucket:
- capacity: max burst
- refill_per_sec: tokens per second
- empty bucket returns 429
Minimal limiter on /api β
cpp
#include <vix.hpp>
#include <vix/middleware/app/presets.hpp>
using namespace vix;
int main()
{
App app;
app.use("/api", middleware::app::rate_limit_custom_dev(5.0, 0.0));
app.get("/api/ping", [](Request& req, Response& res){
res.json({ "ok", true, "msg", "pong", "xff", req.header("x-forwarded-for") });
});
app.run(8080);
}Try:
bash
for i in $(seq 1 6); do
echo "---- $i"
curl -i http://localhost:8080/api/ping
doneCSRF (cookie + header) β
CSRF is relevant mainly for browser sessions using cookies.
Vix default:
- cookie:
csrf_token - header:
x-csrf-token
Minimal CSRF on /api β
cpp
#include <vix.hpp>
#include <vix/middleware/app/presets.hpp>
using namespace vix;
int main()
{
App app;
app.use("/api", middleware::app::csrf_dev());
app.get("/api/csrf", [](Request&, Response& res){
res.header("Set-Cookie", "csrf_token=abc; Path=/; SameSite=Lax");
res.json({ "csrf_token", "abc" });
});
app.post("/api/update", [](Request&, Response& res){
res.json({ "ok", true, "message", "CSRF passed" });
});
app.run(8080);
}Try:
bash
curl -i -c cookies.txt http://localhost:8080/api/csrf
curl -i -b cookies.txt -X POST http://localhost:8080/api/update -d "x=1"
curl -i -b cookies.txt -X POST http://localhost:8080/api/update -H "x-csrf-token: wrong" -d "x=1"
curl -i -b cookies.txt -X POST http://localhost:8080/api/update -H "x-csrf-token: abc" -d "x=1"Complete example (Session + RBAC + Rate limit) β
This is a single file that shows:
- cookie sessions for browser style auth
- RBAC protected admin API using JWT
- rate limit on /api
Save as security_complete.cpp.
cpp
#include <chrono>
#include <iostream>
#include <string>
#include <vix.hpp>
#include <vix/middleware/app/presets.hpp>
#include <vix/middleware/app/adapter.hpp>
#include <vix/middleware/auth/jwt.hpp>
#include <vix/middleware/auth/rbac.hpp>
using namespace vix;
// Example admin token (HS256, secret=dev_secret)
// payload: {"sub":"user123","roles":["admin"],"perms":["products:write"]}
static const std::string TOKEN_OK =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9."
"eyJzdWIiOiJ1c2VyMTIzIiwicm9sZXMiOlsiYWRtaW4iXSwicGVybXMiOlsicHJvZHVjdHM6d3JpdGUiXX0."
"w1y3nA2F1kq0oJ0x8wWc5wQx8zF4h2d6V7mYp0jYk3Q";
static void install_session(App& app)
{
app.use(middleware::app::session_dev(
"dev_session_secret",
"sid",
std::chrono::hours(24 * 7),
false,
"Lax",
true,
"/",
true
));
}
static void install_api_security(App& app)
{
// Rate limit all /api traffic
app.use("/api", middleware::app::rate_limit_dev(60, std::chrono::minutes(1)));
// JWT + RBAC only for /api/admin
vix::middleware::auth::JwtOptions jwt_opt{};
jwt_opt.secret = "dev_secret";
jwt_opt.verify_exp = false;
vix::middleware::auth::RbacOptions rbac_opt{};
rbac_opt.require_auth = true;
rbac_opt.use_resolver = false;
auto jwt_mw = vix::middleware::app::adapt_ctx(vix::middleware::auth::jwt(jwt_opt));
auto ctx_mw = vix::middleware::app::adapt_ctx(vix::middleware::auth::rbac_context(rbac_opt));
auto role_mw = vix::middleware::app::adapt_ctx(vix::middleware::auth::require_role("admin"));
auto perm_mw = vix::middleware::app::adapt_ctx(vix::middleware::auth::require_perm("products:write"));
app.use(vix::middleware::app::when(
[](const Request& r){ return r.path().rfind("/api/admin", 0) == 0; },
std::move(jwt_mw)
));
app.use(vix::middleware::app::when(
[](const Request& r){ return r.path().rfind("/api/admin", 0) == 0; },
std::move(ctx_mw)
));
app.use(vix::middleware::app::when(
[](const Request& r){ return r.path().rfind("/api/admin", 0) == 0; },
std::move(role_mw)
));
app.use(vix::middleware::app::when(
[](const Request& r){ return r.path().rfind("/api/admin", 0) == 0; },
std::move(perm_mw)
));
}
static void install_routes(App& app)
{
app.get("/", [](Request&, Response& res){
res.send(
"Vix security complete example:\n"
" GET /session increments a counter stored in a signed cookie\n"
" GET /api/ping rate limited\n"
" GET /api/admin/stats requires JWT + role admin + perm products:write\n"
);
});
app.get("/session", [](Request& req, Response& res){
auto& s = req.state<middleware::auth::Session>();
int n = s.get("n") ? std::stoi(*s.get("n")) : 0;
s.set("n", std::to_string(++n));
res.text("n=" + std::to_string(n));
});
app.get("/api/ping", [](Request&, Response& res){
res.json({ "ok", true, "pong", true });
});
app.get("/api/admin/stats", [](Request& req, Response& res){
auto& authz = req.state<vix::middleware::auth::Authz>();
res.json({
"ok", true,
"sub", authz.subject,
"is_admin", authz.has_role("admin"),
"can_write_products", authz.has_perm("products:write")
});
});
}
int main()
{
App app;
install_session(app);
install_api_security(app);
install_routes(app);
std::cout
<< "Running:\n"
<< " http://localhost:8080/\n"
<< " http://localhost:8080/session\n"
<< " http://localhost:8080/api/ping\n"
<< " http://localhost:8080/api/admin/stats\n\n"
<< "Admin token:\n " << TOKEN_OK << "\n\n"
<< "Try:\n"
<< " curl -i http://localhost:8080/session\n"
<< " curl -i http://localhost:8080/api/ping\n"
<< " curl -i http://localhost:8080/api/admin/stats\n"
<< " curl -i -H \"Authorization: Bearer " << TOKEN_OK << "\" http://localhost:8080/api/admin/stats\n";
app.run(8080);
return 0;
}Run:
bash
vix run security_complete.cpp