Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
今さら聞けないSPAのCORS対策の話
Search
Sota Sugiura
November 25, 2017
Technology
13k
27
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
今さら聞けないSPAのCORS対策の話
東京Node学園2017にて発表しました。
Sota Sugiura
November 25, 2017
More Decks by Sota Sugiura
See All by Sota Sugiura
内製したSlack Appで頑張るIncident Response@Waroom Meetup #1 / Incident Response with Slack App in 10X
sota1235
0
2k
20220926_セキュリティチームの今_for_Drs._Prime_公開用.pdf
sota1235
0
200
再発防止策を考える技術 / #phpconsen
sota1235
10
4.1k
How to choose the best npm module for your team?
sota1235
9
670
Realtime Database for high traffic production application
sota1235
7
4.3k
Road to migrate JP Web as a microservice
sota1235
4
1.8k
インターフェース再入門 / Think Interface again
sota1235
6
11k
再発防止策を考える技術 #phpconfuk_rej
sota1235
1
1.4k
Update around Firebase #io18
sota1235
3
4.5k
Other Decks in Technology
See All in Technology
AICoEでAIネイティブ組織への進化
yukiogawa
0
170
AI Driven AI Governance
pict3
0
350
AI時代の開発生産性は、個人技からチーム設計へ
moongift
PRO
3
270
最適な自走を最小限の支援で — M&Aで拡大する組織で少人数SREが挑んだ1年 / SRE NEXT 2026
genda
0
1.1k
kintone の AI コワーカーを、 Anthropic にエージェントを"ホストさせて"作った話 #devkinmeetup
sugimomoto
0
100
「ちゃんとやっている」は独りよがりだった ― 不安に寄り添うインシデント対応へ / Towards incident response that addresses anxieties
chmikata
1
5.1k
DMM.com 購入改善推進チーム におけるCodeRabbitを用いた レビューフロー改善の一例
ysknsid25
2
620
Kaggleで成長するために意識したこと
prgckwb
2
320
SREとQA 二人三脚で進めるSLO運用/sre-qa-slo
sugitak
0
500
「最後に責任を取るのはチーム」— 人間のPRレビューを最小化してアップデートしたメンタルモデル
jnishime_dresscode
0
600
公式ドキュメントの歩き方etc
coco_se
0
100
AI Agent SaaS を支える自社仮想化基盤への挑戦と実運用 / ai-agent-saas-virtualization
flatt_security
2
3.8k
Featured
See All Featured
Bridging the Design Gap: How Collaborative Modelling removes blockers to flow between stakeholders and teams @FastFlow conf
baasie
0
610
Navigating the moral maze — ethical principles for Al-driven product design
skipperchong
2
410
A designer walks into a library…
pauljervisheath
211
24k
How to build a perfect <img>
jonoalderson
1
5.8k
How To Speak Unicorn (iThemes Webinar)
marktimemedia
1
500
The Art of Programming - Codeland 2020
erikaheidi
57
14k
HDC tutorial
michielstock
2
740
Navigating Weather and Climate Data
rabernat
0
320
The State of eCommerce SEO: How to Win in Today's Products SERPs - #SEOweek
aleyda
2
11k
Money Talks: Using Revenue to Get Sh*t Done
nikkihalliwell
0
320
The World Runs on Bad Software
bkeepers
PRO
72
12k
Visual Storytelling: How to be a Superhuman Communicator
reverentgeek
2
590
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