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 APIJSON 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.
Schema logico APP
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.it
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.
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.
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.
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)
Developer toolbar OPTIONSDeveloper toolbar POST
Soluzione
Per superare l'ostacolo dobbiamo modificare il server (API) e il client (static) per gestire le chiamate CORS
Server
Il server è sviluppato in NodeJS in JavaScript ed è volutamente semplice solo per dimostrare l'uso dei metodi HTTP e delle intestazioni CORS
JavaScript: 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 + '/');
Lato client dobbiamo modificare la fetchJavaScript 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:
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.