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
660
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
2026TECHFRESH畢業分享會 - 葬送的通靈師:化系統與用戶雜訊成行動訊號
line_developers_tw
PRO
0
1.3k
秘密度ラベル初心者が第1歩でつまづかないための「設計・運用」ポイント
seafay
PRO
0
210
ザ・データベース、MySQL ~ OSC 2026 Sendai ~
sakaik
0
140
AWS Security Agent といっしょに脅威モデリングをやってみよう
amarelo_n24
1
180
データサイエンスを価値につなげるプロジェクト設計 〜 DS一年目が現場で得た気づき 〜
ysd113
1
280
入門!AWS Blocks
ysuzuki
1
160
白金鉱業Meetup_Vol.24_「AIエージェントは分けるほど良い」は本当か? / Is it true that “the more you divide AI agents, the better”?
brainpadpr
1
410
Chainlitで作るお手軽チャットUI
ynt0485
0
280
攻撃者視点で考えるDetection Engineering
cryptopeg
3
2k
Agile and AI Redmine Japan 2026
hiranabe
3
280
Android の公式 Skill / Android skills
yanzm
0
160
【セミナー資料】Claude Code をセキュアに使うための考え方と設定の勘どころ / Claude Code Webinar 20260616
masahirokawahara
2
420
Featured
See All Featured
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
16
2k
The untapped power of vector embeddings
frankvandijk
2
1.8k
From Legacy to Launchpad: Building Startup-Ready Communities
dugsong
0
230
Stewardship and Sustainability of Urban and Community Forests
pwiseman
0
230
Tell your own story through comics
letsgokoyo
1
960
Design of three-dimensional binary manipulators for pick-and-place task avoiding obstacles (IECON2024)
konakalab
0
460
Making Projects Easy
brettharned
120
6.7k
Test your architecture with Archunit
thirion
1
2.3k
The Limits of Empathy - UXLibs8
cassininazir
1
360
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
47
8.2k
Art, The Web, and Tiny UX
lynnandtonic
304
22k
Ecommerce SEO: The Keys for Success Now & Beyond - #SERPConf2024
aleyda
1
2k
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