Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
今さら聞けない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
220
再発防止策を考える技術 / #phpconsen
sota1235
10
4.1k
How to choose the best npm module for your team?
sota1235
9
680
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
積み重なった技術負債への挑戦 〜初手としての全社ゴト化〜
techtekt
PRO
0
1.2k
日経電子版を支えていく Kasane Design System/fec_fukuoka
nikkei_engineer_recruiting
0
1.5k
synctest時代のhttptest Go 1.27で変わるHTTPサーバテストの裏側 / go conference2026 synctest and httptest
budougumi0617
1
3k
10Xに技術的負債をもたらした「2つの境界の歪み」その構造と解消への営み
10xinc
0
1.9k
Minecraft JavaのMODをSwiftで作る
1mash0
0
160
HHKBエバンジェリストになる方法
941
0
100
OpenTelemetryのメトリクスをCloudWatchに送ってPromQLで見てみた
ota1022
0
150
ユーザー価値を届け続けるためにウォンテッドリーが大切にしている文化
kotaminato
0
160
作品が生態系になった ─ Mini Tokyo 3D から世界へ
nagix
0
190
TinyGo 開発サイクルを高速化する:Go で作るエミュレータ入門
zozotech
PRO
1
740
Spring BootからQuarkusへの移行
tatsuya1bm
2
120
Deployment の 先にある AI Agent 基盤 - kagent vNext、Agent Substrate、Hermes から読み解く Agent Runtime の現在地 / k8s-matsuri-2-ai-agent-platform-amsy810
masayaaoyama
3
550
Featured
See All Featured
jQuery: Nuts, Bolts and Bling
dougneiner
66
8.6k
Ten Tips & Tricks for a 🌱 transition
stuffmc
1
230
ラッコキーワード サービス紹介資料
rakko
1
4.9M
Agile that works and the tools we love
rasmusluckow
331
22k
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
12
1.3k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
333
23k
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
11
1k
Kristin Tynski - Automating Marketing Tasks With AI
techseoconnect
PRO
0
520
SEO Brein meetup: CTRL+C is not how to scale international SEO
lindahogenes
2
2.9k
Large-scale JavaScript Application Architecture
addyosmani
515
110k
The Cult of Friendly URLs
andyhume
79
7k
Leo the Paperboy
mayatellez
10
2.3k
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