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. 今さら聞けないSPAのCORS対策の話
    Making SPA with thinking about CORS
    @sota1235

    View Slide

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

    View Slide

  3. By the way

    View Slide

  4. View Slide

  5. 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

    View Slide

  6. CORS

    View Slide

  7. Cross
    Origin
    Resource
    Sharing

    View Slide

  8. Cross
    Origin
    Resource
    Sharing

    View Slide

  9. What’s origin?

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  16. Origin
    ! IUUQTIPHFDPN IUUQTIPHFDPNGVHBIUNM
    IUUQTIPHFDPN IUUQIPHFDPN
    IUUQIPHFDPN IUUQIPHFDPN
    IUUQIPHFDPN IUUQNPHFIPHFDPN

    View Slide

  17. Origin for what?

    View Slide

  18. Same Origin Policy

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  25. 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

    View Slide

  26. Error occurred

    View Slide

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

    View Slide

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

    View Slide

  29. 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

    View Slide

  30. So, CORS

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  37. 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

    View Slide

  38. 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

    View Slide

  39. 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

    View Slide

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

    View Slide

  41. 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

    View Slide

  42. BTW, Today’s theme is
    CORS with SPA

    View Slide

  43. 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

    View Slide

  44. Not good

    View Slide

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

    View Slide

  46. 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

    View Slide

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

    View Slide

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

    View Slide

  49. 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

    View Slide

  50. 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  56. 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?

    View Slide

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

    View Slide

  58. 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

    View Slide

  59. Let’s use cookie! But…
    5IFSFJTOPcookieIFBEFS

    View Slide

  60. 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  65. mmm, still not perfect

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  69. 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

    View Slide

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

    View Slide

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

    View Slide

  72. 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

    View Slide

  73. 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

    View Slide

  74. 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

    View Slide

  75. 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

    View Slide

  76. 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

    View Slide

  77. 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

    View Slide

  78. Then, how to optimize?

    View Slide

  79. –Sota Sugiura
    l/PUIJOHJTUIFCFTUBSDIJUFDUVSFGPS
    BQQMJDBUJPO`TQFSGPSNBODFz

    View Slide

  80. 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

    View Slide

  81. 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  88. 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

    View Slide

  89. 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

    View Slide

  90. 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

    View Slide

  91. 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

    View Slide

  92. That’s all

    ✅ Basic settings for CORS
    • ✅ Ajax with credentials
    • ✅ Avoid using Preflight Request

    ✅ Or cache request for optimizing

    View Slide

  93. 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

    View Slide

  94. Thank you

    View Slide