Chiamate CORS con Node JS
Le nuove tendenze per lo sviluppo di applicazioni web, prevedono di separare l'interfaccia utente e la logica applicativa/accesso ai dati, in due o più applicazioni.
Ad esempio l'interfaccia utente è su un dominio, supponiamo static.sgart.it, e contiene solo file HTML, CSS, JavaScript, immagini... insomma solo file statici tipicamente realizzata con dei framework come Angular o React.
La parte di logica di business e di accesso ai dati è demandata ad un altra applicazione (o più anche distribuite nel mondo), ad esempio api.sgart.it che espongono delle API JSON che vengono consumate dalla parte statica.
otteniamo l'errore
Per funzionare è necessario che il server, api.sgart.it, ritorni nell'header delle specifiche intestazioni (headers) che autorizzino il browser ad effettuare la chiamata dal dominio statis.sgart.it.
Le intestazioni sono queste:
dove
Quando il browser deve eseguire una chiamata AJAX su un altro dominio, la prima volta nella sessione, come prima cosa esegue una chiamata HTTP di tipo OPTIONS (preflight request), se il server risponde con le intestazioni corrette prosegue con la normale chiamata
Per aggirare l'ostacolo e e creare un ambiente il più possibile simile a quello reale, si può modificare il file C:\Windows\System32\drivers\etc\hosts e includere i nomi dei 2 domini in modo che puntino a 127.0.0.1, ad esempio:
Infatti chiunque potrebbe richiamare l'API, ad esempio da Powershell.
Questo solo per sottolineare che le intestazioni CORS non sono una forma di autenticazione e/o autorizzazioni delle API. Vengono interpretate solo dal browser per effettuare chiamate in sicurezza.
Prossimamente mostrerò un esempio che utilizza l'autenticazione tramite JSON Web Token per autenticare ed autorizzare il client ad accedere alle API.
Il progetto completo è disponibile su GitHub.
Ad esempio l'interfaccia utente è su un dominio, supponiamo static.sgart.it, e contiene solo file HTML, CSS, JavaScript, immagini... insomma solo file statici tipicamente realizzata con dei framework come Angular o React.
La parte di logica di business e di accesso ai dati è demandata ad un altra applicazione (o più anche distribuite nel mondo), ad esempio api.sgart.it che espongono delle API JSON che vengono consumate dalla parte statica.
Le due parti potrebbero essere ospitate su Azure usando per la parte statica le Static Web Apps e per le API le Functions.
Cross-Origin Resource Sharing (CORS)
Qui sorge un problema, se da una pagina HTML del domino static.sgart.it cerchiamo di fare una chiamata AJAX usando fetch verso api.sgart.itJavaScript
fetch("https://api.sgart.it", {
method: "POST",
headers: { "Content-Type": "application/json" },
})
.then((response) => response.json())
.then((data) => { ... })
.catch((error) => { ... });
TypeError: NetworkError when attempting to fetch resource.
CORS Allow Origin Not Matching Origin
NS_ERROR_DOM_BAD_URI
Questa è una restrizione di sicurezza del browser.CORS Allow Origin Not Matching Origin
NS_ERROR_DOM_BAD_URI
Per funzionare è necessario che il server, api.sgart.it, ritorni nell'header delle specifiche intestazioni (headers) che autorizzino il browser ad effettuare la chiamata dal dominio statis.sgart.it.
Le intestazioni sono queste:
Text: CORS Headers
Access-Control-Allow-Origin: http://static.sgart.it
Access-Control-Allow-Headers: Content-Type,Authorization
Access-Control-Allow-Methods: OPTIONS,GET,POST
- Access-Control-Allow-Origin: specifica il dominio che verrà autorizzare a contattare il server dal browser
- Access-Control-Allow-Headers: definisce quali header sono accettati nella richiesta
- Access-Control-Allow-Methods: definisce quali metodi HTTP sono permessi
L'intestazione importante è http://static.sgart.it:8080
che deve coincidere con il dominio del sito statico senza nessuna slash finale, ad esempio http://static.sgart.it:8080/
non va bene.
Per semplicità, per la demo, ho utilizzato solo il protocollo http.
Nella realtà i produzione, per la massima sicurezza, andrà utilizzato solo il protocollo https sia per risorse interne all'azienda sia per i siti pubblici.
Nella realtà i produzione, per la massima sicurezza, andrà utilizzato solo il protocollo https sia per risorse interne all'azienda sia per i siti pubblici.
Quando il browser deve eseguire una chiamata AJAX su un altro dominio, la prima volta nella sessione, come prima cosa esegue una chiamata HTTP di tipo OPTIONS (preflight request), se il server risponde con le intestazioni corrette prosegue con la normale chiamata
Il browser, nella richiesta, aggiunge sempre l'header Origin con la url del dominio del sito corrente (senza slash finale)
Soluzione
Per superare l'ostacolo dobbiamo modificare il server (API) e il client (static) per gestire le chiamate CORSServer
Il server è sviluppato in NodeJS in JavaScript ed è volutamente semplice solo per dimostrare l'uso dei metodi HTTP e delle intestazioni CORSJavaScript: server.js
const http = require('http');
const fs = require('fs');
const path = require('path');
const port = process.env.PORT || '3000';
http.createServer(function (request, response) {
console.log('request starting ' + request.url + '...'); //visualizzo nella console
const headers = {
'Access-Control-Allow-Origin': 'http://static.sgart.it:8080', // non mettere la slash finale
'Access-Control-Allow-Headers': 'Content-Type,Authorization',
'Access-Control-Allow-Methods': 'OPTIONS,GET,POST',
'Access-Control-Max-Age': 86400, // 1 days * 24 ore * 60 minuti * 60 secondi
'Vary': 'Origin, Access-Control-Request-Headers, Access-Control-Request-Method'
/* altri header se necessario */
};
console.log('---------------------------------');
console.log(`Request [${request.method}] ${new Date()}`, request.headers);
if (request.method === 'OPTIONS') {
console.log('preflight request');
response.writeHead(204, headers);
response.end();
return;
}
if (['GET', 'POST'].indexOf(request.method) > -1) {
const content = { date: new Date() };
console.log('content', content);
response.writeHead(200, headers);
response.end(JSON.stringify(content), 'utf-8');
return;
}
response.writeHead(405, headers);
response.end(`${request.method} is not allowed for the request.`);
}).listen(port); //imposto il server per ascoltare sulla porta indicata
console.log('Server running at http://127.0.0.1:' + port + '/');
Client
Lato client dobbiamo modificare la fetch JavaScript per includere il parametro mode: "cors":HTML: index.html
<html>
<head>
<title>Demo Client CORS - Sgart.it</title>
</head>
<body>
<h1>Demo chiamate AJAX su un dominio differente</h1>
<h3>Risultato chiamata</h3>
<hr />
<div id="result"></div>
<hr />
<button id="btn-reload" type="button">Reload</button>
<script>
function load() {
const elem = document.getElementById("result");
//fetch("http://cors.localhost:3000/", { // non funziona per le restrizioni del browsers
fetch("http://api.sgart.it:3000", {
method: "POST",
mode: "cors", // parametro vincolante per le chiamate cross domain
headers: {
"Content-Type": "application/json",
//Authorization: "Bearer mH4eyJdb...r2E2JRHDcdfxjoYtg",
},
//body: JSON.stringify({ d: 1 }),
})
.then((response) => response.json())
.then((data) => {
console.log(data);
elem.innerHTML = JSON.stringify(data);
})
.catch((error) => {
console.error(error);
elem.innerHTML = error;
});
}
document.addEventListener("DOMContentLoaded", function () {
document
.getElementById("btn-reload")
.addEventListener("click", function () {
load();
});
load();
});
</script>
</body>
</html>
Note
Le chiamate CORS sono bloccate sull'indirizzo localhost.Per aggirare l'ostacolo e e creare un ambiente il più possibile simile a quello reale, si può modificare il file C:\Windows\System32\drivers\etc\hosts e includere i nomi dei 2 domini in modo che puntino a 127.0.0.1, ad esempio:
Text: C:\Windows\System32\drivers\etc\hosts
...
127.0.0.1 static.sgart.it
127.0.0.1 api.sgart.it
Autenticazione
Quello fin qui descritto è valido per applicazioni che non espongono dati riservati e che non richiedono un autenticazione.Infatti chiunque potrebbe richiamare l'API, ad esempio da Powershell.
Questo solo per sottolineare che le intestazioni CORS non sono una forma di autenticazione e/o autorizzazioni delle API. Vengono interpretate solo dal browser per effettuare chiamate in sicurezza.
Prossimamente mostrerò un esempio che utilizza l'autenticazione tramite JSON Web Token per autenticare ed autorizzare il client ad accedere alle API.
Il progetto completo è disponibile su GitHub.