Introduction to the

To Chettuzai, cross-domain is a familiar term. Although the browser is concerned about the security of our website, but we often have to bypass this limitation in order to be able to be accessed by users, CORS is one of the commonly used solutions to cross domain.

You can set access-Control-allow-credentials: true and xhr.withCredentials = true to transfer cookies across domains and save user login status. This scheme is good, but there is a risk of CSRF if not used properly. So, starting with Chrome 51, a SameSite property has been added to the browser’s cookies to protect against CSRF attacks and user tracking.

This feature is currently turned off by default, but some users are already affected. If you are unable to use third-party logins on certain sites, check to see if this is affecting you.

Experience personally

disableSameSitevalidation

While it’s official that this feature will be turned on by default starting with Chrome 79 (previously announced as starting with Chrome 80, this has been changed), testing has found that some users still have this feature turned off by default, so let’s disable SameSite validation first and see what happens.

Open Chrome Settings, disable Chrome ://flags/#same-site-by-default-cookies, and restart the browser.

Use the example code at the end of this article to simulate a login operation locally to get a Cookie across domains and then carry the Cookie to get user information.

As you can see, we can normally use cookies written by the server to send requests and get user information, but we see a warning message on the Console.

Following the programmer’s “warning can be ignored” rule, it seems we can leave this feature alone. But once SameSite is fully enabled in Chrome and users have upgraded the browser, cookie-based cross-domain login sites will not be accessible. So let’s simulate that.

To enable theSameSitevalidation

Also open Chrome Settings, enable Chrome ://flags/#same-site-by-default-cookies, and restart the browser.

Clear cookies and log in again. Note: Cookies are under back-end domain names. Do not clear cookies corresponding to front-end domain names.

In Response Cookies, the SameSite property has a message telling us that the SameSite property is not set and the default value Lax will be used.

Retrieving user information at this point will fail because the Cookie is not brought to the back-end service with the request. Upon examination, it is found that the Cookie has not been successfully written to the user’s browser.

Therefore, if we do not want to fail in 2020, then we need to start dealing with this problem in advance now.

To deal withSameSitevalidation

The default value of the SameSite property Lax only allows get requests to carry cookies, which is obviously not enough, so we changed the value of the SameSite property to None and set the Secure property to true. This also means that your backend service domain must be accessed using HTTPS.

// Note: The cookie module must be updated to the latest version (0.4.0) to support sameSite= None
res.cookie('token'.'token 123', { maxAge: 2592000000.httpOnly: true.sameSite: 'none'.secure: true});Copy the code

Try again and the problem is solved.

However, this is only a stopgap measure because of the settingsameSiteforNoneAfter that,CSRFThe risk is back. So, change totokenIs not dependent onCookie“May be a more reasonable solution.

Complete sample

Here is the complete sample code and Nginx configuration.

app.js

var path = require('path');
var cors = require('cors');
var express = require('express');
var cookieParser = require('cookie-parser');

var app = express();

app.use(cors({ origin: true.credentials: true,})); app.use(express.json()); app.use(express.urlencoded({extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public'))); // index.html is placed in the public directory

app.get('/api/info'.function login(req, res) {
    let token = req.cookies['token'];

    if (token) {
        res.json({ success: true.data: { name: 'somebody'.age: 21}}); }else {
        res.json({ success: false.message: 'Please log in first'}); }}); app.get('/api/login'.function login(req, res) {
    res.cookie('token'.'token 123', { maxAge: 2592000000.httpOnly: true}); res.end(); }); app.get('/api/login/security'.function login(req, res) {
    // Note: The cookie module must be updated to the latest version (0.4.0) to support sameSite= None
    res.cookie('token'.'token 123', { maxAge: 2592000000.httpOnly: true.sameSite: 'none'.secure: true}); res.end(); }); app.listen(8888.function () {
    console.log('http://localhost:8888');
});
Copy the code

index.html

<html>

<head>
  <title>Demo</title>
  <style>
    button {
      width: 80px;
      height: 32px;
      line-height: 32px;
      text-align: center;
    }
  </style>
</head>

<body>
<div>
  <p>
    <button id="login">The login</button>
    <button id="security">Secure login</button>
  </p>
  <p>
    <button id="check">The query</button>
  </p>
</div>

<script>
  (function() {
    var get = function get(url, callback) {
      var xhr = new XMLHttpRequest();
      xhr.withCredentials = true;
      xhr.open('get', url);
      xhr.onreadystatechange = function onreadystatechange() {
        if (xhr.readyState === 4) {
          var res = xhr.response;

          try {
            res = JSON.parse(res);
          } catch (e) {}

          typeof callback === 'function'&& callback(res); }}; xhr.send(null);
    };

    var login = document.querySelector('#login');
    var check = document.querySelector('#check');
    var security = document.querySelector('#security');

    login.addEventListener('click'.function onLogin() {
      get('https://api.server.cn/login');
    });

    check.addEventListener('click'.function onLogin() {
      get('https://api.server.cn/info'.function callback(res) {
        if (res.success) {
          console.log(res.data);
        } else{ alert(res.message); }}); }); security.addEventListener('click'.function onLogin() {
      get('https://api.server.cn/login/security'); }); }) ();</script>
</body>

</html>
Copy the code

nginx.conf

server { listen 443 ssl; server_name api.server.cn; ssl_certificate /path/to/ssl/server.crt; ssl_certificate_key /path/to/ssl/server.key; ssl_ciphers HIGH:! aNULL:! MD5; location / { proxy_pass http://localhost:8888/api/; }}Copy the code

hosts

127.0.0.1 API. Server. CnCopy the code

Pay attention to

Since our certificate is self-signed and cannot really pass the certificate verification of the browser, we need to manually click “Continue to XXX (unsafe)” to send the request to the back-end service normally.

reference

  • Customize domain names to access local services
  • HTTPS on localhost with NGINX
  • CookietheSameSiteattribute