Per uno sviluppatore esperto, questo testo potrebbe non essere affatto sorprendente, ma credo che molti degli articoli che ho letto sull'impostazione di CORS in Rails dicessero qualcosa del tipo: usate rack-cors, consentite a qualsiasi host di accedere all'API e (facoltativamente): dovreste prendere in considerazione qualcosa di diverso (rispetto al consentire qualsiasi host) in produzione.
La proposta codice era sempre vicino a quello sottostante:
config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
permettere di fare
origini ''
resource '', header: :any, methods: :any
fine
fine
e, purtroppo, questi testi difficilmente ci spiegavano cosa fare concretamente in produzione.
Me la cavo abbastanza bene con il copia-incolla (A volte scherzo sul fatto che le aziende potrebbero assumere un copy-paster di Stack Overflow.), nella misura in cui c'è un momento di "riflessione e adattamento" tra il "copia" e l'"incolla". Vorrei quindi spiegare meglio cosa stiamo facendo e come funziona nella vita reale.
Spero che non vi dispiaccia se inizio con una breve introduzione alla teoria dell'onore e poi passo agli esempi di Rails.
Introduzione
Partiamo dall'inizio. Per spiegare meglio le cose, ho diviso l'introduzione in tre parti. La prima parte illustrerà cos'è un'origine, il termine chiave per ciò che stiamo discutendo. La seconda riguarda la SOP, con una breve descrizione. E l'ultima parte parla della CORS
stesso.
Che cos'è un'origine?
Secondo i documenti web MDN:
- L'origine del contenuto Web è definita dallo schema (protocollo), dall'host (dominio) e dalla porta dell'URL utilizzato per accedervi. Due oggetti hanno la stessa origine solo se lo schema, l'host e la porta corrispondono (fonte)
Sembra abbastanza chiaro, non è vero? Analizziamo due esempi tratti da MDN, per sicurezza.
http://example.com/app1/index.html
, http://example.com/app2/index.html
I due casi sopra citati hanno la stessa origine perché:
- i loro schemi (http) sono gli stessi,
- i loro domini (example.com) sono gli stessi,
- le loro porte (implicite) sono le stesse.
http://www.example.com
, http://myapp.example.com
Questi due elementi hanno un'origine diversa perché i domini (www.example.com
, myapp.example.com
) sono diversi.
Spero che sia abbastanza chiaro. In caso contrario, si consiglia di consultare i documenti Web MDN per ulteriori esempi.
Che cos'è la SOP?
I documenti web MDN dicono (fonte):
- Il criterio della stessa origine è un meccanismo di sicurezza critico che limita il modo in cui un documento o uno script caricato da un'origine può interagire con una risorsa di un'altra origine. Aiuta a isolare i documenti potenzialmente dannosi, riducendo i possibili vettori di attacco.
- Le scritture cross-origin sono tipicamente consentite. Esempi sono i link, i reindirizzamenti e l'invio di moduli.
- L'incorporazione di origini incrociate è tipicamente consentita.
- Le letture incrociate sono in genere vietate, ma l'accesso alla lettura è spesso reso invisibile dall'embedding.
Utilizzo CORS
per consentire l'accesso da un'origine all'altra
Come si può notare, nelle definizioni di SOP si parla molto di comportamento trasversale. Non c'è problema. Tutto ciò che dobbiamo sapere ora è che la stessa origine ha più privilegi e che possiamo allentare le regole per le origini incrociate usando CORS. E qui entra in gioco la prossima sezione.
Che cos'è il CORS?
Sulla base delle parole di MDN:
- Il Cross-Origin Resource Sharing (CORS) è un meccanismo basato sulle intestazioni HTTP che consente a un server di indicare qualsiasi altra origine (dominio, schema o porta) diversa dalla propria da cui un browser dovrebbe consentire il caricamento delle risorse. CORS si basa anche su un meccanismo in base al quale i browser effettuano una richiesta di "preflight" al server che ospita la risorsa di origine incrociata, al fine di verificare che il server permetta la richiesta effettiva. In questo preflight, il browser invia delle intestazioni che indicano il metodo HTTP e le intestazioni che saranno utilizzate nella richiesta effettiva. (fonte).
Questo non è ancora sufficiente. Ciò che non è stato detto esplicitamente è che l'intestazione più importante quando si usa CORS
è Controllo dell'accesso - Consenti origine
:
- Il
Controllo dell'accesso - Consenti origine
indica se la risposta può essere condivisa con il codice richiedente dell'origine indicata (fonte).
Bene, questo dovrebbe essere tutto. Nella vita reale, quando si configura CORS
, in genere si configura il file ACAO
prima l'intestazione.
Vita reale
Questo è tutto per quanto riguarda le definizioni. Torniamo a Rails e agli esempi reali.
Come configurare CORS in Rails?
Utilizzeremo sicuramente i rack-cors (come ci è stato detto). Ricordiamo il primo frammento, quello che viene fornito più spesso in altri articoli:
config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
permettere di fare
origini ''
resource '', header: :any, methods: :any
fine
fine
Il numero di opzioni è vasto o addirittura infinito, ma consideriamo queste due:
- stiamo costruendo l'API che può essere utilizzata da client di browser di terze parti,
- abbiamo la tipica separazione frontend/backend e vogliamo consentire ai nostri clienti fidati di accedere all'API.
API di costruzione a cui accedono clienti di terze parti
Se vi trovate di fronte alla prima opzione, probabilmente potreste optare per origini
'*' - volete che altri costruiscano un client sulla base della vostra API e non sapete chi sono, giusto?
Tipica separazione frontend/backend
Se state sviluppando quest'ultima, probabilmente non volete che tutti facciano richieste cross-originarie alla vostra API. Piuttosto volete che:
- consentire ai client di produzione di accedere all'API di produzione,
- Lo stesso vale per l'allestimento,
- lo stesso per localhost,
- si potrebbe voler consentire alle app di revisione FE di accedere allo staging.
Utilizzeremo ancora i portapacchi (come ci è stato detto), ma a modo nostro.
Utilizziamo 2 variabili ENV: ORIGINI_CONSENTITE
per le definizioni letterali dell'origine (un asterisco o un URL vero e proprio) e ORIGINE_CONSENTITA_REGEXPS
per i modelli.
config/initializers/cors.rb
frozenstringliteral: true
toregexp = ->(stringa) { Regexp.new(stringa) }
hosts = [
*ENV.fetch('ALLOWEDORIGINS').split(','),
*ENV.fetch('ALLOWEDORIGINREGEXPS').split(';').map(&to_regexp)
]
Rails.application.config.middleware.insert_before 0, Rack::Cors do
consentire
origini(*hosts)
risorsa '*',
metodi: %i[get post put patch delete options head],
intestazioni: :qualsiasi
fine
fine
Cosa sta succedendo qui?
- Come si può notare, stiamo dividendo i valori definiti nelle variabili ENV con diversi separatori. Questo perché è meno probabile che il punto e virgola compaia nello schema di definizione dell'URL.
- I valori letterali sono pronti per l'uso, ma dobbiamo mappare i modelli in modo che siano istanze di Regexp.
- Poi, uniamo il tutto e permettiamo a questi host di accedere a qualsiasi risorsa con i metodi whitelisted utilizzati dalla nostra API.
Questo dovrebbe garantire una flessibilità sufficiente per definire i valori corretti negli ambienti di sviluppo, staging e produzione.
Conclusioni
Riassumiamo tutto quanto sopra in punti chiave:
- utilizzare le variabili ENV per configurare
CORS
,
- utilizzare espressioni regolari per consentire a origini diverse di accedere all'API di staging (ad esempio, per le applicazioni di revisione),
- mettere sempre "pensa e aggiusta" tra "copia" e "incolla".
Ecco fatto. Buona giornata!
Per saperne di più:
Perché si dovrebbe (probabilmente) usare Typescript?
10 startup di New York da menzionare nel 2021