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
210
再発防止策を考える技術 / #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
Agent 時代の Kaggle 展望 / kaggle-in-the-agentic-era
upura
0
120
サイバー捜査員研修(前半)
nomizone
1
1.6k
AWS ネットワーク構築でハマった(ハマりかけた) 5選とそこから得た教訓
nagisa53
4
180
メルカリのグローバルアプリで挑んだ AlloyDB 運用と課題解決の実践記
hatappi
0
230
組織にどうSREを根付かせるか?〜IVRyの場合〜
abnoumaru
0
320
AIエージェントを前提としたプラットフォーム エンジニアリング:GKEで作るAgent-Ready Golden Path
legalontechnologies
PRO
1
170
Pavlokで始める電撃駆動開発
sgrsn
0
170
第3回しろおびセキュリティスポンサーセッション
log0417
0
140
Breaking the Seal: Static Deobfuscation of Compiled V8 JavaScript Bytecode Malware
hshrzd
0
480
AI駆動開発は個人技からチーム戦へ:組織でAIを使いこなすための実践設計
moongift
PRO
0
440
AIは実装を速くする。では、私たちは何を今作るべきか?-立場を越えてリリースに向き合ったチーム開発の実践 / 20260801 Hiromi Nakaya and Naoki Takahashi
shift_evolve
PRO
3
380
QAエンジニア起点で進める、SmartHRにおける信頼性向上について
kaomi_wombat
1
100
Featured
See All Featured
HU Berlin: Industrial-Strength Natural Language Processing with spaCy and Prodigy
inesmontani
PRO
0
600
The Anti-SEO Checklist Checklist. Pubcon Cyber Week
ryanjones
0
200
Designing Dashboards & Data Visualisations in Web Apps
destraynor
232
55k
DevOps and Value Stream Thinking: Enabling flow, efficiency and business value
helenjbeal
1
290
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
16
2.1k
Tips & Tricks on How to Get Your First Job In Tech
honzajavorek
1
660
職位にかかわらず全員がリーダーシップを発揮するチーム作り / Building a team where everyone can demonstrate leadership regardless of position
madoxten
64
56k
State of Search Keynote: SEO is Dead Long Live SEO
ryanjones
0
240
A Guide to Academic Writing Using Generative AI - A Workshop
ks91
PRO
1
360
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
254
22k
Are puppies a ranking factor?
jonoalderson
1
3.8k
Build The Right Thing And Hit Your Dates
maggiecrowley
39
3.4k
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