🧩 Middleware - Progettazione¶
Struttura del middleware¶
Fondamentalmente è composto da 2 funzioni all'interno dello stesso .prg:
- Setup: qualche variabile che definiamo di default e che possiamo riconfigurare se necessario
- Middleware: funzione che eseguirà il processo e può usare la variabile di setup
Questa potrebbe essere la progettazione di un 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.
...
// Uso di s_MyVar
...
RETURN lAccept
Dinamiche di progettazione per tipo di applicazione¶
Non tutte le applicazioni hanno bisogno degli stessi middleware. La scelta dipende da chi consuma l'API e come.
Qui mostriamo alcuni esempi di progettazione in base allo scenario.
Applicazione Web classica (HTML + form)¶
L'utente interagisce da un browser. Lo stato viene salvato in sessione con cookie.
Request del browser
│
├─ MW_BodyLimit ← limita la dimensione (upload)
├─ MW_Session ← carica la sessione dal cookie
├─ MW_Csrf ← verifica il token CSRF su POST/PUT/DELETE
├─ MW_RequireAuth ← c'è una sessione attiva?
└─ MW_RequireRole ← ha il ruolo necessario?
L'ordine conta: carica prima la sessione, poi verifica il CSRF (che ha bisogno della sessione), e solo dopo controlla l'autenticazione.
API REST (JSON, client esterno)¶
Il client è un'app mobile, SPA o servizio esterno. Nessun cookie di sessione: l'autenticazione è stateless con JWT o API Key.
Request del client API
│
├─ MW_Cors ← header CORS per il browser
├─ MW_RateLimit ← protezione contro abusi
├─ MW_BodyLimit ← limita la dimensione del body
├─ MW_Jwt ← valida il token Bearer
├─ MW_RequireAuth ← il token è valido?
└─ MW_RequireRole ← ruolo sufficiente?
Servizio interno (machine-to-machine)¶
Comunicazione tra servizi nello stesso sistema. Nessun utente umano, nessuna sessione. L'autenticazione è tramite API Key statica.
Request del servizio interno
│
├─ MW_BodyLimit ← protezione di base
├─ MW_ApiKey ← valida X-Api-Key
└─ MW_RequireAuth ← chiave conosciuta?
Cosa proteggere e cosa no¶
Proteggere sempre¶
| Cosa? | Con cosa? |
|---|---|
| Route private (pannello, dati utente) | Autenticazione + ruoli |
| Endpoint che modificano dati (POST/PUT/DELETE) | CSRF sul web, JWT/ApiKey sull'API |
| Upload di file o payload grandi | Body limit |
| Endpoint pubblici con traffico elevato | Rate limiting |
| Qualsiasi cosa che restituisca dati sensibili | Header di sicurezza HTTP |
Non proteggere troppo¶
Un errore comune è applicare tutti i middleware a tutte le route come precauzione. Il risultato è una latenza aggiunta inutile e codice più difficile da debug.
La pagina di benvenuto pubblica non ha bisogno di JWT. Un endpoint di health-check non ha bisogno di sessione. Gli asset statici non hanno bisogno di CSRF.
La regola è semplice: applica il middleware minimo necessario per il livello di fiducia che quella route richiede.
Esempi concettuali¶
Esempio semplice: logging delle request¶
Il middleware più semplice possibile non blocca nulla. Osserva e logga solamente:
Arriva la request
│
▼
[MW_ReqLog]
Annota: method + path + IP nel log
Ritorna sempre .T.
│
▼
Handler - viene eseguito normalmente
Utile per la tracciabilità: sapere quali route vengono chiamate, con quale frequenza, da quali IP.
Esempio composito: route API protetta¶
Una route API che possono usare solo utenti autenticati con ruolo editor:
POST /api/articles (crea articolo)
│
▼
[MW_RateLimit]
Questo IP ha superato 100 req/min?
No → .T., continua
│
▼
[MW_Jwt]
C'è l'header Authorization: Bearer xxx?
Il token è valido e non scaduto?
Sì → deposita il payload in oCtx:hData["jwt"] → .T.
No → risponde 401 → .F. → break
│
▼
[MW_RequireRole("editor")]
oCtx:hData["jwt"]["role"] == "editor"?
Sì → .T.
No → risponde 403 → .F. → break
│
▼
Handler _CreateArticle()
Sa già che l'utente è valido e ha il permesso.
Si preoccupa solo di creare l'articolo.
Tre middleware, tre responsabilità chiare, codice di business pulito.