Slide 1

Slide 1 text

今さら聞けないSPAのCORS対策の話 Making SPA with thinking about CORS @sota1235

Slide 2

Slide 2 text

console.log(me) • Sota Sugiura • @sota1235 • Mercari, Inc. • My dream is to be JavaScript

Slide 3

Slide 3 text

By the way

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

CORS

Slide 7

Slide 7 text

Cross Origin Resource Sharing

Slide 8

Slide 8 text

Cross Origin Resource Sharing

Slide 9

Slide 9 text

What’s origin?

Slide 10

Slide 10 text

Origin • Defined from 3 part • Scheme • Host • Port

Slide 11

Slide 11 text

Origin https://example.com:80

Slide 12

Slide 12 text

Origin https://example.com:80 Scheme Host Port

Slide 13

Slide 13 text

Origin https://example.com:80 http://example.com:80

Slide 14

Slide 14 text

Origin https://example.com:80 http://example.com:80 Different scheme

Slide 15

Slide 15 text

Origin https://example.com:80 http://example.com:80 Different scheme then different Origin

Slide 16

Slide 16 text

Origin ! IUUQTIPHFDPN IUUQTIPHFDPNGVHBIUNM IUUQTIPHFDPN IUUQIPHFDPN IUUQIPHFDPN IUUQIPHFDPN IUUQIPHFDPN IUUQNPHFIPHFDPN

Slide 17

Slide 17 text

Origin for what?

Slide 18

Slide 18 text

Same Origin Policy

Slide 19

Slide 19 text

Same Origin Policy • Controlling interactions between different origins • The content in the origin will be protected from other origin • Important feature for security

Slide 20

Slide 20 text

SOP for… • XMLHttpRequest • Fetch API • Images drawn on Canvas • and so on

Slide 21

Slide 21 text

Cross Origin Fetch API http://localhost:9000 http://localhost:8000

Slide 22

Slide 22 text

Cross Origin Fetch API http://localhost:9000 http://localhost:8000 #SPXTFS "1*

Slide 23

Slide 23 text

Cross Origin Fetch API http://localhost:9000 http://localhost:8000 /PUTBNF0SJHJO

Slide 24

Slide 24 text

Cross Origin Fetch API http://localhost:9000 http://localhost:8000 )551(&5

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

Error occurred

Slide 27

Slide 27 text

What happened? • Browser blocked JavaScript to get HTTP response Resource Access Response

Slide 28

Slide 28 text

What happened? • Browser blocked JavaScript to get HTTP response • Even if server returns response Resource Access Response

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

So, CORS

Slide 31

Slide 31 text

CORS • Accessing to different origin with CORS • Controlling access permission with HTTP headers • Made for more flexible frontend interuction

Slide 32

Slide 32 text

Cross Origin http://localhost:9000 http://localhost:8000

Slide 33

Slide 33 text

Cross Origin http://localhost:9000 http://localhost:8000 const apiServer = 'http://localhost:8000'; fetch(apiServer) .then(res => { console.log(res); }) .catch(err => { // Do nothing });

Slide 34

Slide 34 text

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}`); });

Slide 35

Slide 35 text

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}`); });

Slide 36

Slide 36 text

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 });

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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 :)

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

BTW, Today’s theme is CORS with SPA

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

Not good

Slide 45

Slide 45 text

We need to understand • CORS • preflight • Ajax with credentials • When we understand, we can explain it to server side engineer by own

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

Let’s make sample SPA http://localhost:9000 http://localhost:8000 眢 4FDVSF 眢 ,FFQVTFSTFTTJPO 眢 (PPEQFSGPSNBODF #SPXTFS "1*

Slide 48

Slide 48 text

Step 1 - Cross Origin Step 2 - User Session Step 3 - Optimize performance

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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}`); });

Slide 52

Slide 52 text

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}`); });

Slide 53

Slide 53 text

Step 2 - User Session • Want to identify user on server side • Authentication, permission • Controlling access to some endpoints

Slide 54

Slide 54 text

How to mange session? • Access token? • Cookie? • Or other way?

Slide 55

Slide 55 text

Access Token? • Save in anywhere? • Local Storage? Or something like it? accessToken=abcd1234

Slide 56

Slide 56 text

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?

Slide 57

Slide 57 text

Cookie is better way • We don’t need to implement special logic • We can protect cookie from XSS • Surely, we MUST use https

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

Let’s use cookie! But… 5IFSFJTOPcookieIFBEFS

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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();

Slide 62

Slide 62 text

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();

Slide 63

Slide 63 text

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}`); });

Slide 64

Slide 64 text

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}`); });

Slide 65

Slide 65 text

mmm, still not perfect

Slide 66

Slide 66 text

“*” 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!

Slide 67

Slide 67 text

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}`); });

Slide 68

Slide 68 text

Step 3 - Optimize performance • Before optimizing your application’s performance, you need to know about preflight request

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

Request Flow 015*0/4 $034JOGPSNBUJPO ① Preflight Request (&5 3FTQPOTF

Slide 71

Slide 71 text

Request Flow 015*0/4 $034JOGPSNBUJPO ② Request you want to send (&5 3FTQPOTF

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

Then, how to optimize?

Slide 79

Slide 79 text

–Sota Sugiura l/PUIJOHJTUIFCFTUBSDIJUFDUVSFGPS BQQMJDBUJPO`TQFSGPSNBODFz

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

For CSRF • Adding origin checking on server • Using preflight request • The thing you should think about is not to exec request before any checking

Slide 83

Slide 83 text

CSRF http://localhost:10000 http://localhost:8000 #BE3FRVFTU

Slide 84

Slide 84 text

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}`); });

Slide 85

Slide 85 text

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}`); });

Slide 86

Slide 86 text

You need to use preflight? • In some case, you can’t avoid using preflight request • Don’t worry, you can optimize around preflight request

Slide 87

Slide 87 text

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 });

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

That’s all • ✅ Basic settings for CORS • ✅ Ajax with credentials • ✅ Avoid using Preflight Request • ✅ Or cache request for optimizing

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

Thank you