Vix.cpp v2.6.0 is here Read the blog
Skip to content

Security

The security group protects HTTP routes before and after handlers run.

It covers browser security, cross-origin access, CSRF protection, IP filtering, and rate limiting.

Authentication answers:

txt
Who is making the request?
1

Security answers:

txt
Should this HTTP request be allowed to reach this route?
Should this response include safer browser headers?
Should this client be slowed down or blocked?
1
2
3

The security middleware lives under:

cpp
namespace vix::middleware::security
1

When using vix::App, prefer the App helpers:

cpp
namespace vix::middleware::app
1

What security provides

The security group includes:

MiddlewarePurpose
headers()Add browser security headers to responses
cors()Control cross-origin browser access
csrf()Protect unsafe methods with a cookie/header token check
ip_filter()Allow or deny requests based on client IP headers
rate_limit()Limit request frequency per client key

For normal vix::App applications, use the App presets:

cpp
middleware::app::security_headers_dev()
middleware::app::cors_dev(...)
middleware::app::csrf_dev(...)
middleware::app::ip_filter_dev(...)
middleware::app::ip_filter_allow_deny_dev(...)
middleware::app::rate_limit_dev(...)
middleware::app::rate_limit_custom_dev(...)
1
2
3
4
5
6
7

Basic security setup

A small API can start with:

cpp
#include <vix.hpp>
#include <vix/middleware.hpp>

using namespace vix;

int main()
{
  App app;

  app.use("/api", middleware::app::security_headers_dev());
  app.use("/api", middleware::app::cors_dev({"https://example.com"}));
  app.use("/api", middleware::app::rate_limit_dev());

  app.get("/api/health", [](Request &, Response &res)
  {
    res.json({
      "ok", true
    });
  });

  app.run(8080);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

This gives /api a basic HTTP security layer:

txt
security headers
  make browser responses safer

CORS
  controls which browser origins can call the API

rate limit
  slows down abusive clients
1
2
3
4
5
6
7
8

A practical order is:

cpp
app.use("/api", middleware::app::security_headers_dev());
app.use("/api", middleware::app::cors_dev({"https://example.com"}));
app.use("/api", middleware::app::rate_limit_dev());
app.use("/api", middleware::app::body_limit_write_dev(1024 * 1024));
app.use("/api/private", middleware::app::api_key_dev("secret"));
app.use("/api/forms", middleware::app::csrf_dev());
1
2
3
4
5
6

The idea is:

txt
security headers
  can be added to most responses

CORS
  must handle browser preflight requests early

rate limit
  should reject abusive clients before expensive work

body limit
  should reject large bodies before parsers

authentication
  should protect private routes

CSRF
  should protect unsafe browser form/session routes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

The exact order depends on the application.

The principle is stable:

txt
reject invalid requests early
keep route handlers focused
add response hardening consistently
1
2
3

Security headers

headers() adds HTTP response headers that improve browser security.

The App preset is:

cpp
app.use("/api", middleware::app::security_headers_dev());
1

Example:

cpp
#include <vix.hpp>
#include <vix/middleware.hpp>

using namespace vix;

int main()
{
  App app;

  app.use("/api", middleware::app::security_headers_dev());

  app.get("/api/ping", [](Request &, Response &res)
  {
    res.json({
      "ok", true,
      "message", "headers applied"
    });
  });

  app.get("/", [](Request &, Response &res)
  {
    res.text("public route");
  });

  app.run(8080);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

Test:

bash
curl -i http://127.0.0.1:8080/api/ping
1

Typical headers can include:

txt
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: no-referrer
Permissions-Policy: ...
1
2
3
4

Security headers usually run after the handler because they modify the final response.

txt
request
  -> security headers middleware
  -> handler
  -> add headers
  -> response
1
2
3
4
5

Configure security headers

Use the lower-level middleware when you need explicit options.

cpp
vix::middleware::security::SecurityHeadersOptions opt;

opt.x_content_type_options = true;
opt.x_frame_options = true;
opt.referrer_policy = true;
opt.permissions_policy = true;
opt.hsts = false;
opt.content_security_policy = "default-src 'self'";

app.use("/api", vix::middleware::app::adapt_ctx(
  vix::middleware::security::headers(opt)
));
1
2
3
4
5
6
7
8
9
10
11
12

Main options:

OptionPurpose
x_content_type_optionsAdd X-Content-Type-Options: nosniff
x_frame_optionsAdd X-Frame-Options: DENY
referrer_policyAdd Referrer-Policy
permissions_policyAdd Permissions-Policy
hstsAdd Strict-Transport-Security
content_security_policyAdd a custom CSP value

Only enable HSTS when the application is served through HTTPS.

If Nginx or another proxy terminates TLS, make sure the deployment is truly HTTPS-only before enabling HSTS.

CORS

CORS controls which browser origins can call your API.

It matters when a frontend is served from a different origin than the backend.

Example:

txt
frontend
  https://example.com

backend
  http://127.0.0.1:8080
1
2
3
4
5

Install CORS on your API prefix:

cpp
app.use("/api", middleware::app::cors_dev({"https://example.com"}));
1

Example:

cpp
#include <vix.hpp>
#include <vix/middleware.hpp>

using namespace vix;

int main()
{
  App app;

  app.use("/api", middleware::app::cors_dev({"https://example.com"}));

  app.get("/api", [](Request &, Response &res)
  {
    res.json({
      "ok", true
    });
  });

  app.run(8080);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

Test:

bash
curl -i \
  http://127.0.0.1:8080/api \
  -H "Origin: https://example.com"
1
2
3

Expected response headers include:

txt
Access-Control-Allow-Origin: https://example.com
Vary: Origin
1
2

CORS preflight

Browsers send preflight requests for some cross-origin requests.

A preflight request uses:

txt
OPTIONS
Origin
Access-Control-Request-Method
Access-Control-Request-Headers
1
2
3
4

Example:

bash
curl -i \
  -X OPTIONS http://127.0.0.1:8080/api/update \
  -H "Origin: https://example.com" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: Content-Type, X-CSRF-Token"
1
2
3
4
5

Allowed origins should receive a successful preflight response.

Blocked origins should receive an error.

bash
curl -i \
  -X OPTIONS http://127.0.0.1:8080/api/update \
  -H "Origin: https://evil.com" \
  -H "Access-Control-Request-Method: POST"
1
2
3
4

Expected blocked status:

txt
403 Forbidden
1

Common error code:

txt
cors_forbidden
1

Explicit OPTIONS routes

For browser APIs, it is often useful to define explicit OPTIONS routes for endpoints that need preflight.

cpp
app.options("/api/update", [](Request &, Response &res)
{
  res.status(204).send();
});
1
2
3
4

With CORS installed on /api, the CORS middleware can handle the preflight behavior.

Example:

cpp
#include <vix.hpp>
#include <vix/middleware.hpp>

using namespace vix;

int main()
{
  App app;

  app.use("/api", middleware::app::cors_dev({"https://example.com"}));

  app.options("/api/update", [](Request &, Response &res)
  {
    res.status(204).send();
  });

  app.post("/api/update", [](Request &, Response &res)
  {
    res.json({
      "ok", true
    });
  });

  app.run(8080);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

Use explicit OPTIONS routes when you want predictable browser preflight behavior.

Configure CORS

Use the lower-level middleware when you need explicit options.

cpp
vix::middleware::security::CorsOptions opt;

opt.allowed_origins = {"https://example.com"};
opt.allow_any_origin = false;
opt.allow_credentials = true;
opt.allow_methods = {"GET", "POST", "OPTIONS"};
opt.allow_headers = {"Content-Type", "Authorization", "X-CSRF-Token"};
opt.expose_headers = {"X-Request-Id"};
opt.max_age_seconds = 600;
opt.vary_origin = true;

app.use("/api", vix::middleware::app::adapt_ctx(
  vix::middleware::security::cors(opt)
));
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Main options:

OptionPurpose
allowed_originsList of accepted origins
allow_any_originAllow any origin when configured
allow_credentialsAdd Access-Control-Allow-Credentials
allow_methodsMethods accepted during preflight
allow_headersHeaders accepted during preflight
expose_headersResponse headers visible to browsers
max_age_secondsBrowser preflight cache duration
vary_originAdd Vary: Origin

If credentials are enabled, avoid using * as the final Access-Control-Allow-Origin value.

CSRF

csrf() protects unsafe HTTP methods using the double-submit cookie pattern.

The client must send the same token in:

txt
a cookie
a request header
1
2

Default names are usually:

txt
cookie: csrf_token
header: x-csrf-token
1
2

CSRF is most useful for browser-based applications that use cookies or sessions.

Example:

cpp
#include <vix.hpp>
#include <vix/middleware.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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

Get the cookie:

bash
curl -i \
  -c cookies.txt \
  http://127.0.0.1:8080/api/csrf
1
2
3

Fail without the header:

bash
curl -i \
  -b cookies.txt \
  -X POST http://127.0.0.1:8080/api/update \
  -d "x=1"
1
2
3
4

Expected status:

txt
403 Forbidden
1

Pass with matching cookie and header:

bash
curl -i \
  -b cookies.txt \
  -X POST http://127.0.0.1:8080/api/update \
  -H "x-csrf-token: abc" \
  -d "x=1"
1
2
3
4
5

Expected status:

txt
200 OK
1

Common error code:

txt
csrf_failed
1

CSRF with CORS

When CORS and CSRF are used together, install CORS before CSRF.

cpp
app.use("/api", middleware::app::cors_dev({"https://example.com"}));
app.use("/api", middleware::app::csrf_dev("csrf_token", "x-csrf-token", false));
1
2

CORS must be able to handle preflight requests.

CSRF should protect unsafe methods such as:

txt
POST
PUT
PATCH
DELETE
1
2
3
4

Example:

cpp
#include <vix.hpp>
#include <vix/middleware.hpp>

using namespace vix;

int main()
{
  App app;

  app.use("/api", middleware::app::security_headers_dev());
  app.use("/api", middleware::app::cors_dev({"https://example.com"}));
  app.use("/api", middleware::app::csrf_dev("csrf_token", "x-csrf-token", false));

  app.options("/api/update", [](Request &, Response &res)
  {
    res.status(204).send();
  });

  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
    });
  });

  app.run(8080);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

For cross-site cookies in browsers, production deployments may need cookie attributes such as:

txt
SameSite=None
Secure
1
2

Use those only when serving through HTTPS.

Configure CSRF

Use the lower-level middleware when you need explicit options.

cpp
vix::middleware::security::CsrfOptions opt;

opt.cookie_name = "csrf_token";
opt.header_name = "x-csrf-token";
opt.protect_get = false;

app.use("/api", vix::middleware::app::adapt_ctx(
  vix::middleware::security::csrf(opt)
));
1
2
3
4
5
6
7
8
9

Main options:

OptionPurpose
cookie_nameCookie that contains the CSRF token
header_nameHeader expected to contain the CSRF token
protect_getRequire CSRF on GET when set to true

Usually keep protect_get false.

GET routes should normally be safe and side-effect free.

IP filter

ip_filter() allows or denies requests based on a client IP extracted from headers.

Common headers are:

txt
x-forwarded-for
x-real-ip
1
2

This is useful behind reverse proxies, internal APIs, admin endpoints, and private dashboards.

Example:

cpp
#include <vix.hpp>
#include <vix/middleware.hpp>

using namespace vix;

int main()
{
  App app;

  app.use("/api", middleware::app::ip_filter_allow_deny_dev(
    "x-forwarded-for",
    {"10.0.0.1", "127.0.0.1"},
    {"9.9.9.9"},
    true
  ));

  app.get("/", [](Request &, Response &res)
  {
    res.text("public route");
  });

  app.get("/api/hello", [](Request &req, Response &res)
  {
    res.json({
      "ok", true,
      "x_forwarded_for", req.header("x-forwarded-for")
    });
  });

  app.run(8080);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

Allowed IP:

bash
curl -i \
  http://127.0.0.1:8080/api/hello \
  -H "X-Forwarded-For: 10.0.0.1"
1
2
3

Blocked by allow list:

bash
curl -i \
  http://127.0.0.1:8080/api/hello \
  -H "X-Forwarded-For: 1.2.3.4"
1
2
3

Explicitly denied:

bash
curl -i \
  http://127.0.0.1:8080/api/hello \
  -H "X-Forwarded-For: 9.9.9.9"
1
2
3

Expected blocked status:

txt
403 Forbidden
1

Common error codes:

txt
ip_denied
ip_not_allowed
1
2

Configure IP filter

Use the lower-level middleware when you need explicit control.

cpp
vix::middleware::security::IpFilterOptions opt;

opt.header_name = "x-forwarded-for";
opt.allow = {"10.0.0.1", "127.0.0.1"};
opt.deny = {"9.9.9.9"};
opt.use_remote_addr_fallback = true;

app.use("/api", vix::middleware::app::adapt_ctx(
  vix::middleware::security::ip_filter(opt)
));
1
2
3
4
5
6
7
8
9
10

Main options:

OptionPurpose
allowIf non-empty, only listed IPs are allowed
denyDenied IPs are rejected before allow rules
header_nameHeader used to extract the client IP
use_remote_addr_fallbackTry fallback headers such as x-real-ip

Deny rules win before allow rules.

Be careful with proxy headers

Headers such as X-Forwarded-For can be spoofed if clients connect directly to your server.

Use IP filtering with proxy headers only when:

txt
your app is behind a trusted proxy
the proxy overwrites client-provided forwarding headers
direct public access to the app port is blocked
1
2
3

If your app is exposed directly to the internet, do not blindly trust X-Forwarded-For.

Rate limiting

rate_limit() limits how often a client can call a route.

It uses a token bucket model.

A client key is usually derived from a header such as:

txt
x-forwarded-for
x-real-ip
1
2

The App preset is:

cpp
app.use("/api", middleware::app::rate_limit_dev());
1

For demos, use a small capacity and no refill:

cpp
app.use("/api", middleware::app::rate_limit_custom_dev(
  5.0,
  0.0
));
1
2
3
4

Example:

cpp
#include <vix.hpp>
#include <vix/middleware.hpp>

using namespace vix;

int main()
{
  App app;

  app.use("/api", middleware::app::rate_limit_custom_dev(
    5.0,
    0.0
  ));

  app.get("/", [](Request &, Response &res)
  {
    res.text("public route");
  });

  app.get("/api/ping", [](Request &req, Response &res)
  {
    res.json({
      "ok", true,
      "msg", "pong",
      "xff", req.header("x-forwarded-for")
    });
  });

  app.run(8080);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

Test:

bash
for i in $(seq 1 6); do
  echo "---- $i"
  curl -i http://127.0.0.1:8080/api/ping
done
1
2
3
4

The sixth request can return:

txt
429 Too Many Requests
1

Common error code:

txt
rate_limited
1

Common response headers:

txt
X-RateLimit-Limit
X-RateLimit-Remaining
Retry-After
X-RateLimit-Reset
1
2
3
4

Configure rate limit

Use the lower-level middleware when you need exact behavior.

cpp
vix::middleware::security::RateLimitOptions opt;

opt.capacity = 60.0;
opt.refill_per_sec = 1.0;
opt.add_headers = true;
opt.key_header = "x-forwarded-for";

app.use("/api", vix::middleware::app::adapt_ctx(
  vix::middleware::security::rate_limit(opt)
));
1
2
3
4
5
6
7
8
9
10

Main options:

OptionPurpose
capacityMaximum burst size
refill_per_secTokens added per second
add_headersAdd rate limit headers
key_headerHeader used to derive the default client key
key_fnCustom function used to derive the client key

Use key_fn when the key should come from something else, such as a tenant id, authenticated subject, or custom header.

cpp
vix::middleware::security::RateLimitOptions opt;

opt.capacity = 100.0;
opt.refill_per_sec = 10.0;
opt.key_fn = [](const vix::middleware::Request &req)
{
  const std::string tenant = req.header("x-tenant-id");

  return tenant.empty() ? "anonymous" : tenant;
};

app.use("/api", vix::middleware::app::adapt_ctx(
  vix::middleware::security::rate_limit(opt)
));
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Combine CORS, IP filter, and rate limit

A realistic API may combine multiple security layers.

cpp
#include <vix.hpp>
#include <vix/middleware.hpp>

using namespace vix;

int main()
{
  App app;

  app.use("/api", middleware::app::security_headers_dev());
  app.use("/api", middleware::app::cors_dev({"http://localhost:5173"}));

  app.use("/api", middleware::app::ip_filter_allow_deny_dev(
    "x-forwarded-for",
    {},
    {"1.2.3.4"},
    true
  ));

  app.use("/api", middleware::app::rate_limit_custom_dev(
    5.0,
    0.0,
    "x-forwarded-for"
  ));

  app.options("/api/ping", [](Request &, Response &res)
  {
    res.status(204).send();
  });

  app.get("/api/ping", [](Request &req, Response &res)
  {
    res.json({
      "ok", true,
      "ip", req.header("x-forwarded-for")
    });
  });

  app.run(8080);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

Test allowed origin:

bash
curl -i \
  http://127.0.0.1:8080/api/ping \
  -H "Origin: http://localhost:5173" \
  -H "X-Forwarded-For: 9.9.9.9"
1
2
3
4

Test denied IP:

bash
curl -i \
  http://127.0.0.1:8080/api/ping \
  -H "Origin: http://localhost:5173" \
  -H "X-Forwarded-For: 1.2.3.4"
1
2
3
4

Test rate limit:

bash
for i in $(seq 1 6); do
  echo "---- $i"
  curl -i \
    http://127.0.0.1:8080/api/ping \
    -H "Origin: http://localhost:5173" \
    -H "X-Forwarded-For: 9.9.9.9"
done
1
2
3
4
5
6
7

This composition keeps route handlers simple.

The security layer decides whether the request should reach the route.

Complete security example

cpp
#include <vix.hpp>
#include <vix/middleware.hpp>

using namespace vix;

int main()
{
  App app;

  app.use("/api", middleware::app::security_headers_dev());
  app.use("/api", middleware::app::cors_dev({"https://example.com"}));
  app.use("/api", middleware::app::rate_limit_custom_dev(10.0, 1.0));
  app.use("/api/forms", middleware::app::csrf_dev("csrf_token", "x-csrf-token", false));

  app.options("/api/forms/update", [](Request &, Response &res)
  {
    res.status(204).send();
  });

  app.get("/api/health", [](Request &, Response &res)
  {
    res.json({
      "ok", true
    });
  });

  app.get("/api/forms/csrf", [](Request &, Response &res)
  {
    res.header("Set-Cookie", "csrf_token=abc; Path=/; SameSite=Lax");

    res.json({
      "csrf_token", "abc"
    });
  });

  app.post("/api/forms/update", [](Request &, Response &res)
  {
    res.json({
      "ok", true,
      "message", "updated"
    });
  });

  app.run(8080);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

This gives the API:

txt
security headers
CORS
rate limiting
CSRF on form routes
explicit preflight route
1
2
3
4
5

Authentication can be added on top for private routes.

Security vs authentication

Do not put everything in one category.

Security middleware protects the HTTP surface.

txt
CORS
CSRF
security headers
IP filter
rate limit
1
2
3
4
5

Authentication middleware identifies the caller.

txt
API key
JWT
RBAC
sessions
permissions
1
2
3
4
5

They work together.

Example:

cpp
app.use("/api", middleware::app::security_headers_dev());
app.use("/api", middleware::app::cors_dev());
app.use("/api", middleware::app::rate_limit_dev());

app.use("/api/admin", middleware::app::api_key_dev("secret"));
1
2
3
4
5

The security layer protects the HTTP surface.

The authentication layer protects private actions.

Summary

Use the security group to protect routes before handlers do application work.

A good starting stack is:

cpp
app.use("/api", middleware::app::security_headers_dev());
app.use("/api", middleware::app::cors_dev({"https://example.com"}));
app.use("/api", middleware::app::rate_limit_dev());
1
2
3

Add more specific protections when needed:

cpp
app.use("/api/forms", middleware::app::csrf_dev());
app.use("/api/internal", middleware::app::ip_filter_allow_deny_dev(
  "x-forwarded-for",
  {"10.0.0.1"},
  {},
  true
));
1
2
3
4
5
6
7

Remember the model:

txt
security middleware decides whether the request should reach the route
security headers make responses safer
authentication is documented separately
1
2
3

Released under the MIT License.

Morty Proxy This is a proxified and sanitized view of the page, visit original site.