Middleware¶
Middleware¶
How to use Middleware¶
Middleware is a way to intercept requests to perform some operations on them, like authentication, authorization, logging, etc and also act on the response headers.
Middleware can either be attached to individual handlers, attached at the server level (thus applied to all handlers), or both. They will be executed in the order they are attached, and they can stop the request processing by sending a response themselves.
You can have a look at the examples for some use cases.
For example, such middleware would handle authentication and set some attributes on the request to make them available for the next middleware and for the handler which will process the request.
AsyncMiddlewareFunction complexAuth([](AsyncWebServerRequest* request, ArMiddlewareNext next) {
if (!request->authenticate("user", "password")) {
return request->requestAuthentication();
}
request->setAttribute("user", "Mathieu");
request->setAttribute("role", "staff");
next(); // continue processing
// you can act one the response object
request->getResponse()->addHeader("X-Rate-Limit", "200");
});
Here are the list of available middlewares:
AsyncMiddlewareFunction: can convert a lambda function (ArMiddlewareCallback) to a middlewareAsyncAuthenticationMiddleware: to handle basic/digest authentication globally or per handlerAsyncAuthorizationMiddleware: to handle authorization globally or per handlerAsyncCorsMiddleware: to handle CORS preflight request globally or per handlerAsyncHeaderFilterMiddleware: to filter out headers from the requestAsyncHeaderFreeMiddleware: to only keep some headers from the request, and remove the othersLoggerMiddleware: to log requests globally or per handler with the same pattern as curl. Will also record request processing timeAsyncRateLimitMiddleware: to limit the number of requests on a windows of time globally or per handler
See the Middleware example here.
CORS with AsyncCorsMiddleware¶
You can handle CORS preflight requests and headers using AsyncCorsMiddleware.
static AsyncCorsMiddleware cors;
void setup() {
// Configure CORS
cors.setOrigin("http://my-client-app.com");
cors.setMethods("POST, GET, OPTIONS, DELETE");
cors.setHeaders("X-Custom-Header");
cors.setAllowCredentials(true);
cors.setMaxAge(600);
// Add middleware to server (valid for all handlers)
server.addMiddleware(&cors);
// Or add to specific handler only
// server.on("/api/data", HTTP_GET, handleData).addMiddleware(&cors);
}
Rate Limiting with AsyncRateLimitMiddleware¶
You can limit the number of requests within a time window using AsyncRateLimitMiddleware.
static AsyncRateLimitMiddleware rateLimit;
void setup() {
// Allow maximum 5 requests per 10 seconds
rateLimit.setMaxRequests(5);
rateLimit.setWindowSize(10);
// Apply globally
// server.addMiddleware(&rateLimit);
// OR apply to specific sensitive endpoint
server.on("/login", HTTP_POST, handleLogin).addMiddleware(&rateLimit);
}
Authentication with AsyncAuthenticationMiddleware¶
IMPORTANT: Do not use the setUsername() and setPassword() methods on the handlers anymore.
They are deprecated.
These methods were causing a copy of the username and password for each handler, which is not efficient.
Now, you can use the AsyncAuthenticationMiddleware to handle authentication globally or per handler.
AsyncAuthenticationMiddleware authMiddleware;
// [...]
authMiddleware.setAuthType(AsyncAuthType::AUTH_DIGEST);
authMiddleware.setRealm("My app name");
authMiddleware.setUsername("admin");
authMiddleware.setPassword("admin");
authMiddleware.setAuthFailureMessage("Authentication failed");
authMiddleware.generateHash(); // optimization to avoid generating the hash at each request
// [...]
server.addMiddleware(&authMiddleware); // globally add authentication to the server
// [...]
myHandler.addMiddleware(&authMiddleware); // add authentication to a specific handler
See the Auth example here.
Digest authentication: replay protection¶
Digest authentication includes server-side protections against replay attacks, using a stateless scheme (no per-nonce storage, keeping the server's memory footprint constant):
- Signed nonce: each challenge nonce embeds its issuing time and is signed with a per-server-instance
secret (
HMAC-MD5). A nonce not issued by this server is rejected, and an expired nonce triggers a new challenge withstale=true(RFC 7616 §3.3), so compliant clients (browsers, curl) re-authenticate silently without prompting the user again. - Opaque: each challenge carries an opaque derived from the instance secret, identifying the server instance that issued the nonce.
The nonce validity window defaults to 60 seconds (ASYNCWEBSERVER_DIGEST_NONCE_TTL build flag) and can be
tuned at runtime:
Changing the instance secret instantly revokes every nonce in circulation (all clients get re-challenged):
uint8_t secret[16] = {/* 16 random bytes */};
server.setDigestSecret(secret); // kill-switch: invalidates all outstanding nonces
[!NOTE] As with any stateless scheme, a captured
Authorization: Digest ...header remains valid for the remainder of the nonce TTL. The library does not keep the raw request-target in memory, so the headerurifield is not checked by default — to bind a header to the resource it was issued for (RFC 7616 §3.4.6), pass the expected request-target withauthenticate(..., nonce, opaque, uri)(see below). For one-shot semantics, keep the TTL short and serve Digest-protected endpoints over HTTPS.
For custom validation of every digest parameter, AsyncWebServerRequest::authenticate() accepts
optional nonce, opaque and uri arguments — these only apply to the Digest auth scheme and are
ignored otherwise. A NULL argument lets the library validate it automatically, while a non-NULL
value is compared verbatim against the caller-supplied value:
// the library validates nonce and opaque (header uri is left unchecked)
request->authenticate("admin", "admin", "MyApp");
// the caller takes over the uri check and binds the header to a request-target
// (nonce/opaque are still validated by the library)
request->authenticate("admin", "admin", "MyApp", false, NULL, NULL, "/my/custom/uri");
Re-authentication required¶
AsyncWebServerRequest::reAuthRequired() returns true when the last authenticate() call failed
for a reason that a re-challenge can resolve, i.e. re-sending the same credentials against a fresh
challenge will eventually succeed.
With Digest auth: the credentials were correct and the nonce was issued by this server, but it
expired. requestAuthentication() then automatically issues the next challenge with stale=true
(RFC 7616 §3.3), so compliant clients (browsers, curl) re-authenticate silently without prompting
the user again — no special handling is needed from your code:
if (!request->authenticate("admin", "admin", "MyApp")) {
// reAuthRequired() is true when only the nonce expired: the challenge issued
// below will carry stale=true and the client will retry silently
request->requestAuthentication(AsyncAuthType::AUTH_DIGEST, "MyApp");
return;
}
Future authentication mechanisms may reuse the same flag for their own equivalent conditions.
Migration to Middleware to improve performance and memory usage¶
AsyncEventSource.authorizeConnect(...)=> do not use this method anymore: add a commonAsyncAuthorizationMiddlewareto the handler or server, and make sure to add it AFTER theAsyncAuthenticationMiddlewareif you use authentication.AsyncWebHandler.setAuthentication(...)=> do not use this method anymore: add a commonAsyncAuthenticationMiddlewareto the handler or serverArUploadHandlerFunctionandArBodyHandlerFunction=> these callbacks receiving body data and upload and not calling anymore the authentication code for performance reasons. These callbacks can be called multiple times during request parsing, so this is up to the user to now call theAsyncAuthenticationMiddleware.allowed(request)if needed and ideally when the method is called for the first time. These callbacks are also not triggering the whole middleware chain since they are not part of the request processing workflow (they are not the final handler).