π§© Middleware - Design¶
Middleware structure¶
Basically it's made up of 2 functions within the same .prg:
- Setup: Some variable we define by default and can reconfigure if necessary
- Middleware: function that will execute the process and can use the setup variable
This could be the design of a middleware
STATIC s_MyVar := 3600
FUNCTION Mw_FenixSetup( nExpSecs )
IF ValType( nExpSecs ) == "N" .AND. nExpSecs > 0
s_MyVar := nExpSecs
ENDIF
RETURN nil
FUNCTION Mw_Fenix( oCtx )
LOCAL lAccept := .T.
...
// Use of s_MyVar
...
RETURN lAccept
Design dynamics by application type¶
Not all applications need the same middlewares. The choice depends on who consumes the API and how.
Here we show some design examples according to the scenario.
Classic Web Application (HTML + forms)¶
The user interacts from a browser. State is saved in session with cookie.
Browser request
β
ββ MW_BodyLimit β limit size (uploads)
ββ MW_Session β load session from cookie
ββ MW_Csrf β verify CSRF token on POST/PUT/DELETE
ββ MW_RequireAuth β is there an active session?
ββ MW_RequireRole β does it have the necessary role?
Order matters: load the session first, then verify CSRF (which needs the session), and only then check authentication.
REST API (JSON, external client)¶
The client is a mobile app, SPA, or external service. No session cookies: the authentication is stateless with JWT or API Key.
API client request
β
ββ MW_Cors β CORS headers for the browser
ββ MW_RateLimit β protection against abuse
ββ MW_BodyLimit β limit body size
ββ MW_Jwt β validate Bearer token
ββ MW_RequireAuth β is token valid?
ββ MW_RequireRole β sufficient role?
Internal service (machine-to-machine)¶
Communication between services in the same system. No human users, no sessions. Authentication is by static API Key.
Internal service request
β
ββ MW_BodyLimit β basic protection
ββ MW_ApiKey β validate X-Api-Key
ββ MW_RequireAuth β key known?
What to protect and what not to¶
Always protect¶
| What? | With what? |
|---|---|
| Private routes (panel, user data) | Authentication + roles |
| Endpoints that modify data (POST/PUT/DELETE) | CSRF on web, JWT/ApiKey on API |
| File upload or large payloads | Body limit |
| Public endpoints with high traffic | Rate limiting |
| Anything that returns sensitive data | HTTP security headers |
Don't over-protect¶
A common mistake is applying all middlewares to all routes as a precaution. The result is unnecessary added latency and code that's harder to debug.
The public welcome page doesn't need JWT. A health-check endpoint doesn't need session. Static assets don't need CSRF.
The rule is simple: apply the minimum middleware necessary for the level of trust that route requires.
Conceptual examples¶
Simple example: request logging¶
The simplest possible middleware blocks nothing. It only observes and logs:
Request arrives
β
βΌ
[MW_ReqLog]
Notes: method + path + IP in the log
Always returns .T.
β
βΌ
Handler - executes normally
Useful for traceability: knowing what routes are called, how frequently, from what IPs.
Composite example: protected API route¶
An API route that only authenticated users with editor role can use:
POST /api/articles (create article)
β
βΌ
[MW_RateLimit]
Has this IP exceeded 100 req/min?
No β .T., continue
β
βΌ
[MW_Jwt]
Is there an Authorization: Bearer xxx header?
Is the token valid and not expired?
Yes β deposits payload in oCtx:hData["jwt"] β .T.
No β responds 401 β .F. β breaks
β
βΌ
[MW_RequireRole("editor")]
Is oCtx:hData["jwt"]["role"] == "editor"?
Yes β .T.
No β responds 403 β .F. β breaks
β
βΌ
Handler _CreateArticle()
Already knows that the user is valid and has permission.
Only concerned with creating the article.
Three middlewares, three clear responsibilities, clean business code.