Доступ к document.cookie возвращает пустую строку, даже если файлы cookie перечислены в инструментах разработчика с флагом httpOnly, установленным в значение false.

Иногда * при доступе к document.cookie на странице входа я получаю пустую строку, хотя:

  1. файлы cookie перечислены в инструментах разработчика Chrome и Firefox,
  2. Флаг httpOnly интересующего меня файла cookie установлен на false,
  3. путь к файлу cookie, который меня интересует, установлен в '/'.

Желаемое поведение

Одностраничное приложение My React (SPA) имеет страницу входа, которая содержит элемент <form /> для отправки учетных данных для входа в бэкэнд. Когда получен ответ от серверной части и аутентификация прошла успешно, я проверяю, правильно ли установлен файл cookie аутентификации. В этом случае будет запущено перенаправление, показывающее контент для вошедших в систему пользователей.

Фактическое поведение

К сожалению, как и 15% попыток входа в систему, document.cookie возвращает пустую строку, которая предотвращает перенаправление и удерживает пользователя на странице входа. Нажатие F5 не помогает, но при замене пути URL-адреса вручную после успешного запроса на вход (например, обновление «www.website.tld / login» на «www.website.tld / < strong> start ') пользователь перенаправляется на желаемую страницу, предназначенную только для авторизованных пользователей.

Я не могу воспроизвести ошибку вручную. Просто кажется, что это происходит случайно. Но когда это происходит, и я смотрю в консоль разработчика, я вижу все файлы cookie с бэкэнда (настроены правильно).

Дополнительная информация

  • сервер django работает в бэкэнде
  • желаемый файл cookie устанавливается с помощью response.set_cookie('key', 'value', secure=False httponly=False, samesite='strict')
  • Библиотеки JS (axios, response-router)

Связанный:

Страница входа (JSX)

    import React, { useState } from "react";
    import { Redirect } from "react-router-dom";
    import axios from "axios";

    /**
     * We're using cookies.js to read cookies.
     * Source: https://github.com/madmurphy/cookies.js
     */
    function hasItem(sKey) {
      return new RegExp(
        "(?:^|;\\s*)" +
          encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") +
          "\\s*\\="
      ).test(document.cookie);
    }

    export const LoginPage = () => {
      const [isAuthenticated, setIsAuthenticated] = useState(false);
      const [username, setUsername] = useState("");
      const [password, setPassword] = useState("");

      function handleSubmit(e) {
        e.preventDefault();

        function onSuccess(response) {
          // handle response
          // [...]

          // sometimes console.log(document.cookie) returns empty string
          if (hasItem("auth_cookie")) {
            setIsAuthenticated(true);
          } else {
            console.warn("Cookie not found!");
          }
        }

        function onFailure(error) {
          // handle error
        }

        const conf = {
          headers: new Headers({
            "Content-Type": "application/json; charset=UTF-8",
            Origin: window.location.origin
          })
        };

        axios
          .post("/api/login/", { username, password }, conf)
          .then(response => {
            onSuccess(response);
          })
          .catch(error => {
            onFailure(error);
          });
      }

      if (isAuthenticated) {
        return <Redirect to="/start" />;
      }

      return (
        <div className="login-page">
          <form
            name="login-form"
            method="post"
            onSubmit={e => handleSubmit(e)}
            action="api/login"
            target="hiddenFrame"
          >
            <iframe className="invisible-frame" src="" name="hiddenFrame" />
            <div>
              <label htmlFor="username">Email</label>
              <input
                name="username"
                type="text"
                onChange={e => setUsername(e.target.value)}
              />
            </div>
            <div>
              <label htmlFor="password">Password</label>
              <input
                name="password"
                type="password"
                onChange={e => setPassword(e.target.value)}
              />
            </div>

            <button type="submit">Submit</button>
          </form>
        </div>
      );
    };

Маршрутизация (JSX)

    import React from "react";
    import { Route, Redirect } from "react-router-dom";

    const RootLayout = () => {
      return (
        <div className="root-layout">
          <Switch>
            <PublicRoute path="/login" component={LoginPage} />
            <PrivateRoute path="/" component={App} />
          </Switch>
        </div>
      );
    };

    /**
     * Component that handles redirection when user is logged in already
     */
    const PublicRoute = ({ component: ChildComponent, ...remainingProps }) => {
      let isAuthenticated = hasItem("auth_cookie");
      return (
        <Route
          render={props =>
            isAuthenticated ? <Redirect to="/" /> : <ChildComponent {...props} />
          }
          {...remainingProps}
        />
      );
    };

    /**
     * Component that handles redirection when user has been logged out.
     * E.g. when authentication cookie expires.
     */
    const PrivateRoute = ({ component: ChildComponent, ...remainingProps }) => {
      let isAuthenticated = hasItem("auth_cookie");
      return (
        <Route
          render={props =>
            !isAuthenticated ? (
              <Redirect to="/login" />
            ) : (
              <ChildComponent {...props} />
            )
          }
          {...remainingProps}
        />
      );
    };

    const App = () => (
      <Switch>
        <Route exact path="/" render={() => <Redirect to="/start" />} />
        <Route exact path="/start" component={StartPage} />
        <Route exact path="/blog" component={BlogPage} />
        {/*...*/}
      </Switch>
    );

* I know, that's probably not how a post should start...


person davsto    schedule 30.03.2020    source источник
comment
Можете ли вы добавить код, в котором вы это проверяете?   -  person tudor.gergely    schedule 30.03.2020
comment
Спасибо за комментарий, @ tudor.gergely. Я добавил код.   -  person davsto    schedule 31.03.2020
comment
вы можете попробовать добавить withCredentials: true в вашу axios conf?   -  person tudor.gergely    schedule 31.03.2020
comment
Похоже, это может быть связано с одним и тем же сайтом. Вы пытались установить его на слабый? И вообще, я бы посоветовал использовать httponly=True. Внешнему интерфейсу вообще не нужен доступ к файлам cookie аутентификации, и вы должны использовать бэкэнд для аутентификации.   -  person str    schedule 31.03.2020
comment
Установка samesite="lax" (или нет) помогает! Я еще не тестировал все функции, но первое впечатление хорошее. Теперь я смог воспроизвести ошибку. Доступ к файлам cookie работает только тогда, когда я ввожу URL-адрес в браузере вручную или использую закладку. При входе в SPA по ссылке на внешнем сайте файлы cookie недоступны. Является ли страница, содержащая ссылку на SPA, «первой стороной», которая не позволяет мне читать файлы cookie, когда для их флага samesite установлено строгое значение?   -  person davsto    schedule 01.04.2020
comment
Вроде да. Подробнее см. this. Также обратите внимание, что наличие httponly=True файлов cookie будет работать даже с samesite='strict' в SPA (и это также будет немного более безопасно).   -  person str    schedule 01.04.2020


Ответы (1)


У вас возникла проблема с файлами cookie sameSite. См. Объяснение в описании файлов cookie SameSite:

Если вы установите для SameSite значение Strict, ваш файл cookie будет отправляться только в собственном контексте. [...] Когда пользователь находится на вашем сайте, cookie будет отправлен с запросом, как и ожидалось. Однако при переходе по ссылке на ваш сайт, например, с другого сайта или по электронной почте от друга, по этому первоначальному запросу файл cookie не будет отправлен. Это хорошо, если у вас есть файлы cookie, относящиеся к функциям, которые всегда будет стоять за начальной навигацией, такой как изменение пароля или совершение покупки, но слишком ограничивает Promo_shown. Если ваш читатель переходит по ссылке на сайт, он хочет, чтобы файл cookie был отправлен, чтобы можно было применить его предпочтения.

Теперь у вас есть как минимум два варианта:

  • Рекомендуется: Сохраните samesite=strict и проведите рефакторинг вашего клиентского кода. Веб-интерфейсу вообще не нужен доступ к файлам cookie аутентификации, поэтому вы можете установить httponly=True. Затем представьте серверный API, который проверяет файл cookie на основе запросов от клиентского кода. Это дает вам дополнительное преимущество, заключающееся в том, что вы менее уязвимы для XSS-атак, поскольку код внешнего интерфейса не имеет доступа к cookie аутентификации.
  • Не рекомендуется: установите samesite на none или lax.
person str    schedule 01.04.2020
comment
Спасибо, это то, с чем я могу работать! Cookie аутентификации - это всего лишь фиктивный файл, позволяющий веб-интерфейсу решать, какие страницы показывать. Он также используется, например, для перенаправления пользователя на экран входа в систему после удаления файла cookie аутентификации. Для получения фактического содержимого, связанного с пользователем, используется другой более безопасный (сеансовый) файл cookie. Этот проект - моя первая попытка реализовать механизмы аутентификации. У вас есть дополнительные рекомендации по основам аутентификации? У меня сложилось впечатление, что в большинстве онлайн-руководств показан код с большим количеством уязвимостей. - person davsto; 02.04.2020
comment
Безопасность - это сложно. Возможно, одним из лучших ресурсов в этой области является OWASP, и хорошим началом является их серия шпаргалок. - person str; 02.04.2020