Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Speaker Deck
PRO
Sign in
Sign up for free
今さら聞けないSPAのCORS対策の話
Sota Sugiura
November 25, 2017
Technology
27
12k
今さら聞けないSPAのCORS対策の話
東京Node学園2017にて発表しました。
Sota Sugiura
November 25, 2017
Tweet
Share
More Decks by Sota Sugiura
See All by Sota Sugiura
再発防止策を考える技術 / #phpconsen
sota1235
10
3.1k
How to choose the best npm module for your team?
sota1235
9
300
Realtime Database for high traffic production application
sota1235
7
3.4k
Road to migrate JP Web as a microservice
sota1235
4
1.3k
インターフェース再入門 / Think Interface again
sota1235
6
9.5k
再発防止策を考える技術 #phpconfuk_rej
sota1235
1
840
Update around Firebase #io18
sota1235
3
3.8k
Introduction for sonarwhal
sota1235
0
340
JavaScriptユニットテストの理想と現実
sota1235
13
6.7k
Other Decks in Technology
See All in Technology
IBM Cloud Festa Online 2022 Summer
1ftseabass
PRO
0
190
Cloud Foundryの移行先はどこか? オープンソースPaaS探し
kolinz
0
350
MySQL v5.7 勉強会/study-mysql-ver-5-7
andpad
0
2k
ログラスを支える技術的投資の仕組み / loglass-technical-investment
urmot
9
1.9k
Power BI のうらがわ
hanaseleb
1
130
最先端の生成AIから考える、ビジネスにおける10年後のパラダイムシフト
sbtechnight
1
310
Microsoft Data Analytics trends : ”Lakehouse” , ”Data Mesh"
ryomaru0825
2
120
#awsbasics [LT] サーバレスECにおける Step Functions の使い方
miu_crescent
0
830
増田亨さんによる 「設計の考え方とやり方」勉強会オープニング
tsuyok
0
200
CityGMLとFBXの連携で地理空間のエンタメ化
soh_mitian
0
680
eBPFで実現するコンテナランタイムセキュリティ / Container Runtime Security with eBPF
tobachi
PRO
5
1.6k
20220731 如何跟隨開源技術保持你的職涯發展
pichuang
0
120
Featured
See All Featured
Learning to Love Humans: Emotional Interface Design
aarron
261
37k
Streamline your AJAX requests with AmplifyJS and jQuery
dougneiner
127
8.5k
Fantastic passwords and where to find them - at NoRuKo
philnash
27
1.6k
Stop Working from a Prison Cell
hatefulcrawdad
262
17k
ReactJS: Keep Simple. Everything can be a component!
pedronauck
655
120k
StorybookのUI Testing Handbookを読んだ
zakiyama
6
2.5k
ParisWeb 2013: Learning to Love: Crash Course in Emotional UX Design
dotmariusz
100
5.9k
Build The Right Thing And Hit Your Dates
maggiecrowley
19
1.2k
Become a Pro
speakerdeck
PRO
3
900
VelocityConf: Rendering Performance Case Studies
addyosmani
316
22k
Ruby is Unlike a Banana
tanoku
91
9.3k
Reflections from 52 weeks, 52 projects
jeffersonlam
337
17k
Transcript
今さら聞けないSPAのCORS対策の話 Making SPA with thinking about CORS @sota1235
console.log(me) • Sota Sugiura • @sota1235 • Mercari, Inc. •
My dream is to be JavaScript
By the way
None
In this talk… • You can understand… • How Browser
will work for CORS • Which HTTP headers should be provided for SPA • How we can manage cookie or some credential informations
CORS
Cross Origin Resource Sharing
Cross Origin Resource Sharing
What’s origin?
Origin • Defined from 3 part • Scheme • Host
• Port
Origin https://example.com:80
Origin https://example.com:80 Scheme Host Port
Origin https://example.com:80 http://example.com:80
Origin https://example.com:80 http://example.com:80 Different scheme
Origin https://example.com:80 http://example.com:80 Different scheme then different Origin
Origin ! IUUQTIPHFDPN IUUQTIPHFDPNGVHBIUNM IUUQTIPHFDPN IUUQIPHFDPN IUUQIPHFDPN IUUQIPHFDPN IUUQIPHFDPN IUUQNPHFIPHFDPN
Origin for what?
Same Origin Policy
Same Origin Policy • Controlling interactions between different origins •
The content in the origin will be protected from other origin • Important feature for security
SOP for… • XMLHttpRequest • Fetch API • Images drawn
on Canvas • and so on
Cross Origin Fetch API http://localhost:9000 http://localhost:8000
Cross Origin Fetch API http://localhost:9000 http://localhost:8000 #SPXTFS "1*
Cross Origin Fetch API http://localhost:9000 http://localhost:8000 /PUTBNF0SJHJO
Cross Origin Fetch API http://localhost:9000 http://localhost:8000 )551(&5
Cross Origin Fetch API http://localhost:9000 http://localhost:8000 const apiServer = 'http://localhost:8000';
fetch(apiServer) .then(res => { console.log(res); }) .catch(err => { // Do nothing }); )551(&5
Error occurred
What happened? • Browser blocked JavaScript to get HTTP response
Resource Access Response
What happened? • Browser blocked JavaScript to get HTTP response
• Even if server returns response Resource Access Response
It’s SOP • Some requests are rejected if target origin
is different from current origin • Even if server send response, browser will hide data from JavaScript access
So, CORS
CORS • Accessing to different origin with CORS • Controlling
access permission with HTTP headers • Made for more flexible frontend interuction
Cross Origin http://localhost:9000 http://localhost:8000
Cross Origin http://localhost:9000 http://localhost:8000 const apiServer = 'http://localhost:8000'; fetch(apiServer) .then(res
=> { console.log(res); }) .catch(err => { // Do nothing });
Cross Origin http://localhost:9000 http://localhost:8000 const http = require('http'); const PORT
= 8000; const makeResForCORS = (response) => { response.setHeader( 'Access-Control-Allow-Origin', '*' ); }; const server = http.createServer((req, res) => { makeResForCORS(res); res.end('hello'); }); server.listen(PORT, () => { console.log(`Listening on ${PORT}`); });
Cross Origin http://localhost:9000 http://localhost:8000 const http = require('http'); const PORT
= 8000; const makeResForCORS = (response) => { response.setHeader( 'Access-Control-Allow-Origin', '*' ); }; const server = http.createServer((req, res) => { makeResForCORS(res); res.end('hello'); }); server.listen(PORT, () => { console.log(`Listening on ${PORT}`); });
Cross Origin http://localhost:9000 http://localhost:8000 )551(&5 const apiServer = 'http://localhost:8000'; fetch(apiServer)
.then(res => { console.log(res); }) .catch(err => { // Do nothing });
Cross Origin http://localhost:9000 http://localhost:8000 )5510, HTTP/1.1 200 OK Access-Control-Allow-Origin: *
Date: Fri, 24 Nov 2017 08:41:45 GMT Connection: keep-alive Content-Length: 5 hello
Cross Origin http://localhost:9000 http://localhost:8000 )5510, HTTP/1.1 200 OK Access-Control-Allow-Origin: *
Date: Fri, 24 Nov 2017 08:41:45 GMT Connection: keep-alive Content-Length: 5 hello
Cross Origin http://localhost:9000 http://localhost:8000 )5510, HTTP/1.1 200 OK Access-Control-Allow-Origin: *
Date: Fri, 24 Nov 2017 08:41:45 GMT Connection: keep-alive Content-Length: 5 hello "MMPXBDDFTTJOHGSPNBOZPSJHJOT
Specify more details • Access-Control-Allow-Origin • Origin which can access
to resourse • Access-Control-Allow-Methods • HTTP methods client can use for cross origin access • and so on - I will explain others later :)
Recap • Same Origin Policy • Security structure for protecting
resource • CORS • You should set up for servers if you want to share resource across not same Origins
BTW, Today’s theme is CORS with SPA
Do you have these experiences? • Set Access-Control-Allow-Origin: * without
any thinking • Using preflight, but it is not clear… • Creating SPA without understanding about CORS and other techniques around it
Not good
We need to understand • CORS • preflight • Ajax
with credentials • When we understand, we can explain it to server side engineer by own
Let’s make sample SPA • API and static site Origins
are different • We want to manage user session in some way • We want to make cross origin access more secure and more good performance
Let’s make sample SPA http://localhost:9000 http://localhost:8000 眢 4FDVSF 眢 ,FFQVTFSTFTTJPO
眢 (PPEQFSGPSNBODF #SPXTFS "1*
Step 1 - Cross Origin Step 2 - User Session
Step 3 - Optimize performance
Step 1 - Cross Origin • Set up for CORS
access • At least you need to set 3 kinds of headers • The value of headers depends on your application architecture
You should specify • Access-Control-Allow-Origin • Origin which can access
to resourse • Access-Control-Allow-Methods • HTTP methods client can use • Access-Control-Expose-Headers • HTTP headers client can send to server
Set headers const http = require('http'); const PORT = 8000;
const makeResForCORS = (response) => { response.setHeader('Access-Control-Allow-Origin', 'http://localhost:9000'); response.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,PATCH,OPTIONS'); response.setHeader('Access-Control-Expose-Headers', 'X-Custom-Header,X-Node-Festival'); }; const server = http.createServer((req, res) => { makeResForCORS(res); res.end('hello'); }); server.listen(PORT, () => { console.log(`Listening on ${PORT}`); });
Set headers const http = require('http'); const PORT = 8000;
const makeResForCORS = (response) => { response.setHeader('Access-Control-Allow-Origin', 'http://localhost:9000'); response.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,PATCH,OPTIONS'); response.setHeader('Access-Control-Expose-Headers', 'X-Custom-Header,X-Node-Festival'); }; const server = http.createServer((req, res) => { makeResForCORS(res); res.end('hello'); }); server.listen(PORT, () => { console.log(`Listening on ${PORT}`); });
Step 2 - User Session • Want to identify user
on server side • Authentication, permission • Controlling access to some endpoints
How to mange session? • Access token? • Cookie? •
Or other way?
Access Token? • Save in anywhere? • Local Storage? Or
something like it? accessToken=abcd1234
Not best • If there is XSS, it will be
stolen easily • We have no way to protect local data from JavaScript • And it is not easy to implement • Is will be expired or not? • How to implement sign in/sign out logic?
Cookie is better way • We don’t need to implement
special logic • We can protect cookie from XSS • Surely, we MUST use https
Do you worry document.cookie? • You MUST set this header
• Set-Cookie: HttpOnly; Secure; • HttpOnly - Cookie won’t be accessed from JavaScript • Secure - Cookie will be sent on only HTTPS connection
Let’s use cookie! But… 5IFSFJTOPcookieIFBEFS
Ajax with credentials • When you want to send credentials
such as cookie, you need to specify option • Server also need to specify that credentials is allowed to sent
Ajax with credentials const apiServer = 'http://localhost:8000'; // Fetch API
fetch(apiServer, { credentials: 'include', }) .then(res => { console.log(res); }) .catch(err => { // Do nothing }); // XMLHttpRequest const xhr = new XMLHttpRequest(); xhr.open('GET', apiServer, true); xhr.withCredentials = true; xhr.onload = (res) => { console.log(res); }; xhr.send();
Ajax with credentials const apiServer = 'http://localhost:8000'; // Fetch API
fetch(apiServer, { credentials: 'include', }) .then(res => { console.log(res); }) .catch(err => { // Do nothing }); // XHLHttpRequest const xhr = new XMLHttpRequest(); xhr.open('GET', apiServer, true); xhr.withCredentials = true; xhr.onload = (res) => { console.log(res); }; xhr.send();
Server side for sending credentials const http = require('http'); const
PORT = 8000; const makeResForCORS = (response) => { response.setHeader('Access-Control-Allow-Origin', '*'); response.setHeader('Access-Control-Allow-Credentials', 'true'); }; const server = http.createServer((req, res) => { makeResForCORS(res); res.end('hello'); }); server.listen(PORT, () => { console.log(`Listening on ${PORT}`); });
Server side for sending credentials const http = require('http'); const
PORT = 8000; const makeResForCORS = (response) => { response.setHeader('Access-Control-Allow-Origin', '*'); response.setHeader('Access-Control-Allow-Credentials', 'true'); }; const server = http.createServer((req, res) => { makeResForCORS(res); res.end('hello'); }); server.listen(PORT, () => { console.log(`Listening on ${PORT}`); });
mmm, still not perfect
“*” is dead… • You can’t define ‘*’ for Access-Control-
Allow-Origin • Even if you don’t send credentials cross origin, ‘*’ does not make sense • Also for security, let’s define specific origin!
Server side for sending credentials const http = require('http'); const
PORT = 8000; const makeResForCORS = (response) => { response.setHeader('Access-Control-Allow-Origin', 'http://localhost:9000'); response.setHeader('Access-Control-Allow-Credentials', 'true'); }; const server = http.createServer((req, res) => { makeResForCORS(res); res.end('hello'); }); server.listen(PORT, () => { console.log(`Listening on ${PORT}`); });
Step 3 - Optimize performance • Before optimizing your application’s
performance, you need to know about preflight request
Preflight Request • Pre request before accessing to other origin
• It is sent by browser automatically • It means if browser send preflight request, it takes 2RTT to access to the resource
Request Flow 015*0/4 $034JOGPSNBUJPO ① Preflight Request (&5 3FTQPOTF
Request Flow 015*0/4 $034JOGPSNBUJPO ② Request you want to send
(&5 3FTQPOTF
Preflight Request OPTIONS / HTTP/1.1 Host: localhost:8000 User-Agent: Mozilla/5.0 (Macintosh;
Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/ 62.0.3202.94 Safari/537.3620081130 Minefield/3.1b3pre Accept: */* Accept-Language: ja,en-US;q=0.9,en;q=0.8 Accept-Encoding: gzip, deflate, br Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Connection: keep-alive Origin: http://localhost:9000 Referer: http://localhost:9000/ Access-Control-Request-Method: PUT Access-Control-Request-Headers: x-custom-header
Preflight Request OPTIONS / HTTP/1.1 Host: localhost:8000 User-Agent: Mozilla/5.0 (Macintosh;
Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/ 62.0.3202.94 Safari/537.3620081130 Minefield/3.1b3pre Accept: */* Accept-Language: ja,en-US;q=0.9,en;q=0.8 Accept-Encoding: gzip, deflate, br Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Connection: keep-alive Origin: http://localhost:9000 Referer: http://localhost:9000/ Access-Control-Request-Method: PUT Access-Control-Request-Headers: x-custom-header
Preflight Request OPTIONS / HTTP/1.1 Host: localhost:8000 User-Agent: Mozilla/5.0 (Macintosh;
Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/ 62.0.3202.94 Safari/537.3620081130 Minefield/3.1b3pre Accept: */* Accept-Language: ja,en-US;q=0.9,en;q=0.8 Accept-Encoding: gzip, deflate, br Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Connection: keep-alive Origin: http://localhost:9000 Referer: http://localhost:9000/ Access-Control-Request-Method: PUT Access-Control-Request-Headers: x-custom-header
Response for Preflight HTTP/1.1 200 OK Access-Control-Allow-Origin: http://localhost:9000 Access-Control-Allow-Methods: GET,POST,PUT,DELETE,PATCH,OPTIONS
Access-Control-Allow-Headers: X-Custom-Header,X-Node- Festival Access-Control-Allow-Credentials: true Access-Control-Max-Age: 100 Date: Fri, 24 Nov 2017 17:23:24 GMT Connection: keep-alive
Response for Preflight HTTP/1.1 200 OK Access-Control-Allow-Origin: http://localhost:9000 Access-Control-Allow-Methods: GET,POST,PUT,DELETE,PATCH,OPTIONS
Access-Control-Allow-Headers: X-Custom-Header,X-Node- Festival Access-Control-Allow-Credentials: true Access-Control-Max-Age: 100 Date: Fri, 24 Nov 2017 17:23:24 GMT Connection: keep-alive
When? • When you want to use specific HTTP methods
• ex) PUT, DELETE, PATCH, OPTIONS • When you want to use custom HTTP header • When you want to specify Content-Type header except some values
Then, how to optimize?
–Sota Sugiura l/PUIJOHJTUIFCFTUBSDIJUFDUVSFGPS BQQMJDBUJPO`TQFSGPSNBODFz
Decreasing latency • Let’s make your app 2RTT to 1RTT
• Use only GET, POST method • Not using custom HTTP headers • Set specific value to Content-Type
Do not forget CSRF • If you avoid using preflight,
you need to consider about CSRF • Even if browser protect resource from JavaScript, request will be sent to server Bad Request Response
For CSRF • Adding origin checking on server • Using
preflight request • The thing you should think about is not to exec request before any checking
CSRF http://localhost:10000 http://localhost:8000 #BE3FRVFTU
CSRF http://localhost:10000 http://localhost:8000 #BE3FRVFTU const http = require('http'); const PORT
= 8000; const TARGET_ORIGIN = 'http://localhost:9000'; const makeResForCORS = (response) => { response.setHeader( 'Access-Control-Allow-Origin', TARGET_ORIGIN ); }; const server = http.createServer((req, res) => { makeResForCORS(res); if (req.headers.origin !== TARGET_ORIGIN) { res.end('invalid request'); return; } res.end('hello'); }); server.listen(PORT, () => { console.log(`Listening on ${PORT}`); });
CSRF http://localhost:10000 http://localhost:8000 #BE3FRVFTU const http = require('http'); const PORT
= 8000; const TARGET_ORIGIN = 'http://localhost:9000'; const makeResForCORS = (response) => { response.setHeader( 'Access-Control-Allow-Origin', TARGET_ORIGIN ); }; const server = http.createServer((req, res) => { makeResForCORS(res); if (req.headers.origin !== TARGET_ORIGIN) { res.end('invalid request'); return; } res.end('hello'); }); server.listen(PORT, () => { console.log(`Listening on ${PORT}`); });
You need to use preflight? • In some case, you
can’t avoid using preflight request • Don’t worry, you can optimize around preflight request
Cache Preflight http://localhost:9000 http://localhost:8000 1SFGMJHIU const apiServer = 'http://localhost:8000'; fetch(apiServer)
.then(res => { console.log(res); }) .catch(err => { // Do nothing });
Cache Preflight http://localhost:9000 http://localhost:8000 3FTQPOTF HTTP/1.1 200 OK Access-Control-Allow-Origin: http://
localhost:9000 Access-Control-Allow-Methods: GET,POST,PUT,DELETE,PATCH,OPTIONS Access-Control-Allow-Headers: X- Custom-Header,X-Node-Festival Access-Control-Allow-Credentials: true Access-Control-Max-Age: 100 Date: Fri, 24 Nov 2017 17:23:24 GMT Connection: keep-alive
Cache Preflight http://localhost:9000 http://localhost:8000 3FTQPOTF HTTP/1.1 200 OK Access-Control-Allow-Origin: http://
localhost:9000 Access-Control-Allow-Methods: GET,POST,PUT,DELETE,PATCH,OPTIONS Access-Control-Allow-Headers: X- Custom-Header,X-Node-Festival Access-Control-Allow-Credentials: true Access-Control-Max-Age: 100 Date: Fri, 24 Nov 2017 17:23:24 GMT Connection: keep-alive
Cache Preflight http://localhost:9000 http://localhost:8000 3FTQPOTF HTTP/1.1 200 OK Access-Control-Allow-Origin: http://
localhost:9000 Access-Control-Allow-Methods: GET,POST,PUT,DELETE,PATCH,OPTIONS Access-Control-Allow-Headers: X- Custom-Header,X-Node-Festival Access-Control-Allow-Credentials: true Access-Control-Max-Age: 100 Date: Fri, 24 Nov 2017 17:23:24 GMT Connection: keep-alive $BDIFQSFqJHIUSFTVMUGPSTFDPOET
Access-Control-Max-Age • You can let browser to cache preflight result
• But max cache time is limited • Chrome - 10 minutes • Firefox - 24 hours • Specify value for your app’s spec
That’s all • ✅ Basic settings for CORS • ✅
Ajax with credentials • ✅ Avoid using Preflight Request • ✅ Or cache request for optimizing
Summary • CORS is for interactions between different Origins •
When you write frontend code like SPA… • You need to understand around CORS • You can optimize communication with different origin
Thank you