Upgrade to Pro — share decks privately, control downloads, hide ads and more …

今さら聞けないSPAのCORS対策の話

Sota Sugiura
November 25, 2017

 今さら聞けないSPAのCORS対策の話

東京Node学園2017にて発表しました。

Sota Sugiura

November 25, 2017
Tweet

More Decks by Sota Sugiura

Other Decks in Technology

Transcript

  1. 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
  2. Same Origin Policy • Controlling interactions between different origins •

    The content in the origin will be protected from other origin • Important feature for security
  3. 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
  4. What happened? • Browser blocked JavaScript to get HTTP response

    • Even if server returns response Resource Access Response
  5. 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
  6. CORS • Accessing to different origin with CORS • Controlling

    access permission with HTTP headers • Made for more flexible frontend interuction
  7. 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}`); });
  8. 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}`); });
  9. 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
  10. 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
  11. 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
  12. 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 :)
  13. 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
  14. 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
  15. We need to understand • CORS • preflight • Ajax

    with credentials • When we understand, we can explain it to server side engineer by own
  16. 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
  17. Step 1 - Cross Origin Step 2 - User Session

    Step 3 - Optimize performance
  18. 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
  19. 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
  20. 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}`); });
  21. 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}`); });
  22. Step 2 - User Session • Want to identify user

    on server side • Authentication, permission • Controlling access to some endpoints
  23. Access Token? • Save in anywhere? • Local Storage? Or

    something like it? accessToken=abcd1234
  24. 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?
  25. Cookie is better way • We don’t need to implement

    special logic • We can protect cookie from XSS • Surely, we MUST use https
  26. 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
  27. 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
  28. 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();
  29. 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();
  30. 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}`); });
  31. 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}`); });
  32. “*” 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!
  33. 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}`); });
  34. Step 3 - Optimize performance • Before optimizing your application’s

    performance, you need to know about preflight request
  35. 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
  36. 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
  37. 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
  38. 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
  39. 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
  40. 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
  41. 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
  42. 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
  43. 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
  44. For CSRF • Adding origin checking on server • Using

    preflight request • The thing you should think about is not to exec request before any checking
  45. 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}`); });
  46. 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}`); });
  47. You need to use preflight? • In some case, you

    can’t avoid using preflight request • Don’t worry, you can optimize around preflight request
  48. 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
  49. 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
  50. 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
  51. 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
  52. That’s all • ✅ Basic settings for CORS • ✅

    Ajax with credentials • ✅ Avoid using Preflight Request • ✅ Or cache request for optimizing
  53. 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