Voor een ervaren ontwikkelaar is deze tekst misschien helemaal niet verrassend, maar ik denk dat veel artikelen die ik heb gelezen over de CORS setup in Rails iets zeiden in de trant van: gebruik rack-cors, sta elke host toe om toegang te krijgen tot de API, en (optioneel): je zou iets anders moeten overwegen (dan het toestaan van elke host) in productie.
De voorgestelde code was altijd dicht bij die eronder:
config/initialisatoren/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
toestaan do
oorsprong ''
bron '', headers: :any, methods: :any
einde
einde
en helaas legden deze teksten ons nauwelijks uit wat we eigenlijk moesten doen tijdens de productie.
Ik ben best OK met copy-pasten (Ik maak wel eens een grapje dat bedrijven een Stack Overflow copy-paster zouden kunnen inhuren), voor zover er een "denk na en pas aan" moment is tussen "kopiëren" en "plakken". Dus ik wil graag een beetje uitweiden over wat we hier doen en hoe het in het echte leven werkt.
Ik hoop dat je het niet erg vindt dat ik begin met een korte introductie in de eretheorie en dan verder ga met de Rails voorbeelden.
Inleiding
Laten we bij het begin beginnen. Om de dingen beter uit te leggen, heb ik de inleiding opgesplitst in drie delen. In het eerste deel wordt uitgelegd wat een oorsprong is - de sleutelterm voor wat we hier bespreken. Het tweede deel gaat over SOP, gewoon een korte beschrijving. En het laatste deel gaat over de CORS
zelf.
Wat is een oorsprong?
Volgens de MDN Web Docs:
- De oorsprong van webinhoud wordt bepaald door het schema (protocol), de host (domein) en de poort van de URL die wordt gebruikt om toegang te krijgen. Twee objecten hebben alleen dezelfde oorsprong als het schema, de host en de poort overeenkomen (bron)
Dat lijkt vrij duidelijk, nietwaar? Laten we voor de zekerheid twee voorbeelden van MDN analyseren.
http://example.com/app1/index.html
, http://example.com/app2/index.html
De 2 hierboven hebben dezelfde oorsprong omdat:
- hun schema's (http) zijn hetzelfde,
- hun domeinen (example.com) zijn hetzelfde,
- hun poorten (impliciet) zijn hetzelfde.
http://www.example.com
, http://myapp.example.com
Deze 2 hebben een verschillende oorsprong omdat de domeinen (www.example.com
, myapp.example.com
) zijn verschillend.
Ik hoop dat het duidelijk genoeg is. Zo niet, ga dan naar de MDN Web Docs voor meer voorbeelden.
Wat is SOP?
MDN Web Docs zeggen (bron):
- Het same-origin beleid is een kritisch beveiligingsmechanisme dat beperkt hoe een document of script geladen van de ene oorsprong kan interageren met een bron van een andere oorsprong. Het helpt bij het isoleren van mogelijk schadelijke documenten, waardoor mogelijke aanvalsvectoren worden beperkt.
- Cross-origin writes zijn meestal toegestaan. Voorbeelden zijn links, omleidingen en formulierverzendingen.
- Cross-origin embedding is meestal toegestaan.
- Cross-origin reads zijn meestal niet toegestaan, maar leestoegang wordt vaak gelekt door embedding.
Gebruik CORS
om cross-origin toegang toe te staan
Nou, zoals je kunt zien, staat er veel over herkomstoverschrijdend gedrag in de definities van SOP. Dat is niet erg. Alles wat we nu moeten weten is dat dezelfde oorsprong meer privileges heeft en dat we de regels voor cross-origins kunnen versoepelen door CORS te gebruiken. En hier komt de volgende sectie.
Wat is CORS?
Afgaande op de woorden van MDN:
- Cross-Origin Resource Sharing (CORS) is een op HTTP-headers gebaseerd mechanisme waarmee een server een andere bron (domein, schema of poort) dan zijn eigen bron kan aangeven waarvan een browser het laden van bronnen moet toestaan. CORS vertrouwt ook op een mechanisme waarmee browsers een "preflight" verzoek doen aan de server die de cross-origin bron host, om te controleren of de server het eigenlijke verzoek toestaat. In die preflight stuurt de browser headers die de HTTP-methode en headers aangeven die zullen worden gebruikt in de daadwerkelijke aanvraag. (bron).
Dat is nog steeds niet genoeg. Wat daar niet expliciet is gezegd, is dat de belangrijkste header bij het gebruik van CORS
is Toegangsbeheer-oorsprong toestaan
:
- De
Toegangsbeheer-oorsprong toestaan
response header geeft aan of het antwoord kan worden gedeeld met aanvragende code van de opgegeven oorsprong (bron).
Nou, dat zou het moeten zijn. In het echte leven, bij het configureren van CORS
configureren we meestal de ACAO
kop eerst.
Het echte leven
Tot zover de definities. Laten we teruggaan naar Rails en voorbeelden uit de praktijk.
Hoe CORS configureren in Rails?
We zullen zeker rack-cors gebruiken (zoals ons is verteld). Laten we het eerste fragment in herinnering brengen, het fragment dat het vaakst wordt gegeven in andere artikelen:
config/initialisatoren/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
toestaan do
oorsprong ''
bron '', headers: :any, methods: :any
einde
einde
Het aantal opties is enorm of zelfs oneindig, maar laten we deze twee eens bekijken:
- bouwen we de API die gebruikt mag worden door browserclients van derden,
- We hebben een typische frontend/backend scheiding en willen onze vertrouwde klanten toegang geven tot de API.
API bouwen die toegankelijk is voor clients van derden
Als je voor de eerste optie staat, kun je waarschijnlijk gaan voor oorsprong
*' - je wilt dat anderen een client bouwen bovenop jouw API en je weet niet wie ze zijn, toch?
Typische scheiding tussen frontend en backend
Als je het laatste ontwikkelt, wil je waarschijnlijk niet dat iedereen cross-origin requests doet naar je API. Dat wil je liever wel:
- productie-clients toegang geven tot productie-API,
- hetzelfde voor staging,
- hetzelfde voor localhost,
- wilt u misschien FE-review apps toegang geven tot staging.
We zullen nog steeds rek-cors gebruiken (zoals ons is verteld) - maar op onze manier.
Laten we 2 ENV-variabelen gebruiken: TOEGESTANE_OORSPRONG
voor letterlijke oorsprongsdefinities (een sterretje of een echte URL) en TOEGESTANE_OORSPRONG_REGEXPS
voor de patronen.
config/initialisatoren/cors.rb
bevrorenstringliteral: waar
toregexp = ->(string) { Regexp.new(string) }
hosts = [
*ENV.fetch('ALLOWEDORIGINS').split(','),
*ENV.fetch('ALLOWEDORIGINREGEXPS').split(';').map(&to_regexp)
]
Rails.application.config.middleware.insert_before 0, Rack::Cors do
toestaan do
origins(*hosts)
bron '*',
methods: %i[get post put patch delete options head],
headers: :any
einde
einde
Wat is hier aan de hand?
- Zoals je kunt zien, splitsen we de waarden die zijn gedefinieerd in ENV-variabelen op met verschillende scheidingstekens. Dat komt omdat een puntkomma minder vaak voorkomt in het URL-definitiepatroon.
- Letterlijke waarden zijn klaar voor gebruik, maar we moeten de patronen omzetten naar echte Regexp instanties.
- Vervolgens voegen we alles samen en geven we deze hosts toegang tot elke bron met toegestane methoden die onze API gebruikt.
Dit zou je genoeg flexibiliteit moeten geven om de juiste waarden te definiëren in je ontwikkel-, staging- en productieomgevingen.
Conclusies
Laten we al het bovenstaande samenvatten in kernpunten:
- ENV-variabelen gebruiken om te configureren
CORS
,
- reguliere expressies gebruiken om verschillende herkomsten toegang te geven tot staging API (bijvoorbeeld voor review-apps),
- zet altijd "nadenken en aanpassen" tussen "kopiëren" en "plakken".
Dat was het. Nog een fijne dag! 🙂
Lees meer:
Waarom je (waarschijnlijk) Typescript zou moeten gebruiken?
10 NYC Startups die het vermelden waard zijn in 2021