Файл cookie только для HTTP в заголовке ответа от серверной части, но не сохраняется в клиентском браузере

У меня есть полнофункциональное приложение, состоящее из интерфейса React и бэкэнда FastAPI (python), оба из которых в настоящее время обслуживаются локально на 127.0.0.1 (localhost).

БЭКЭНД:

Вот код моего внутреннего сервера с настроенными CORSMiddleware и SessionMiddleware:

from fastapi import FastAPI, HTTPException, status, Request
from fastapi.middleware.cors import CORSMiddleware
from starlette.middleware.sessions import SessionMiddleware
import spotipy
from spotipy.oauth2 import SpotifyOAuth

app = FastAPI()

# generated via cmd: 'openssl rand -hex 32'
SECRET_KEY = "XXX"

origins = [
    "http://localhost",
    "https://localhost",
    "http://localhost:3000",
    "https://localhost:3000",
    "http://127.0.0.1",
    "https://127.0.0.1",
    "http://127.0.0.1:3000",
    "https://127.0.0.1:3000",
]

# https://www.starlette.io/middleware/#corsmiddleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# https://www.starlette.io/middleware/#sessionmiddleware
app.add_middleware(
    SessionMiddleware,
    secret_key=SECRET_KEY,
    https_only=True,
    max_age=3600,  # 3600s = 1hr, the life of spotify access token
    same_site = "none",
)

CLIENT_ID = "YYYY"
CLIENT_SECRET = "ZZZ"
OAUTH_REDIRECT_URI = "http://127.0.0.1:3000/"
SCOPE = "playlist-read-private playlist-read-collaborative user-read-private user-read-email"

# Docs: https://spotipy.readthedocs.io/en/2.24.0/#module-spotipy.oauth2
sp_oauth = SpotifyOAuth(client_id=CLIENT_ID, client_secret=CLIENT_SECRET, redirect_uri=OAUTH_REDIRECT_URI, scope=SCOPE)

@app.post("/spotify-auth", tags=["auth"])
def exchange_token(code: str, request: Request):
    # Exchanges token, with check_cache=False to ensure new users can be registered
    token_info = sp_oauth.get_access_token(code=code, check_cache=False)
    print(token_info)
    # https://www.starlette.io/middleware/#sessionmiddleware
    request.session["access_token"] = token_info["access_token"]
    request.session["refresh_token"] = token_info["refresh_token"]
    return {"tokenSaved": True}

В других конечных точках, например /user, когда я пытаюсь получить доступ к request.session.get("access_token"), я не получаю абсолютно ничего обратно.

ВНЕШНИЙ ИНТЕРФЕЙС:

вот POST-запрос axios из моего интерфейса (реакция/машинопись), который достигнет конечной точки /spotify-auth на моем бэкэнде:

const exchangeSpotifyAuthToken = async (
    code: string,
): Promise<AccessTokenReponse> => {
    const searchParams = new URLSearchParams();
    searchParams.append("code", code);
    const response = await axios.post(
        `${API_URL_BASE}/spotify-auth?` + searchParams.toString(),
        { withCredentials: true },
    );
    return response.data;
};

Но, как я уже сказал, мои попытки использовать access_token в других маршрутах, которые должны быть доступны через request.session.get("access_token"), не увенчались успехом. Каждый из моих запросов axios также включает эту конфигурацию { withCredentials: true }, так что это не должно быть проблемой.

ВЕЩИ, которые я пробовал:

Я видел много сообщений, связанных с CORS, касающихся этой проблемы, и думаю, что реализовал требования следующим образом:

  • CORSMiddleware разрешил источники на бэкэнде, настроенные на получение запросов от моего интерфейса (127.0.0.1:3000)
  • allow_credentials=True на бэкэнде
  • Фронтенд и бэкэнд расположены в одном домене: 127.0.0.1.
  • запросы от интерфейса, настроенные с помощью withCredentials: true

ИНСТРУМЕНТЫ БРАУЗЕРА:

Я также проверил вкладку «Сеть» в инструментах браузера и увидел, что мой сеанс правильно записывается после моего POST-запроса на вход/аутентификацию:

Но любые последующие запросы к моему серверу не содержат этот файл cookie сеанса, поэтому они сразу же не проходят проверку подлинности:

Разве мой файл cookie сеанса не должен отображаться в этих заголовках запросов, чтобы мой сервер мог их получить? Что я делаю не так?

Спасибо!

Ни один современный браузер не будет принимать попытки небезопасного веб-источника, например https://127.0.0.1:8000, создать файл cookie с пометкой Secure; браузеры молча отвергают такие попытки. Вот почему, несмотря на наличие заголовка Set-Cookie, рассматриваемый файл cookie не создается в браузере.

jub0bs 26.06.2024 18:52

Вы можете проверить, действительно ли браузер сохранил файл cookie. Он находится на вкладке «Хранилище» инструментов разработки, но из-за разных доменов вам, возможно, придется сначала «открыть» localhost:8000.

tevemadar 26.06.2024 18:55

@jub0bs localhost часто имеет особые правила, Developer.mozilla.org/en-US/docs/Web/HTTP/Headers/…

tevemadar 26.06.2024 18:56

Также имеет значение, насколько сложна ваша реальная страница, например, установка файлов cookie изнутри <iframe> может быть заблокирована в зависимости от браузера и/или его настроек (вы можете добиться успеха с Firefox, но если вы когда-нибудь встретите пользователя Safari, он просто сломается). .

tevemadar 26.06.2024 19:02

на снимке экрана заголовка запроса вы можете видеть, что httponly и secure установлены, поэтому браузер должен принимать файлы cookie... но когда я проверяю «Cookies» на вкладке хранилища инструментов разработки, их нет. Так что это явно не принимается. Что я делаю не так? Я четко следую всей документации FastAPI, MDN CORS и т. д.

tprebenda 26.06.2024 19:14

@tevemadar Спасибо; Я этого не знал. Очень интересно. Я прочитал все RFC, связанные с файлами cookie, но не помню, чтобы читал в них об этом исключении. Кажется, это отклонение от RFC, которые браузеры решили реализовать. По моим тестам, 127.0.0.1 тоже работает. Интересно, все ли петлевые IP-адреса работают...

jub0bs 26.06.2024 19:27

@tevemadar Чего бы это ни стоило, я реализовал подобные исключения для localhost и IP-адресов обратной связи в моей библиотеке промежуточного программного обеспечения CORS для Go.

jub0bs 26.06.2024 19:41

@tprebenda, если вы не совсем против Chrome, вы можете попробовать эту штуку. Кроме того, у него есть отдельное представление chrome://settings/content/all, где вы можете увидеть, есть ли файл cookie для localhost:8000 или нет (хотя в этом представлении вы не можете увидеть его содержимое, только если оно существует).

tevemadar 27.06.2024 12:29

Помимо ссылки, приведенной выше, вы также можете найти этот ответ и этот ответ также полезными.

Chris 28.06.2024 05:09
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
9
51
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

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

В конечном итоге у меня в SessionMiddleware было domain = ".localhost", вот так:

app.add_middleware(
    SessionMiddleware,
    secret_key=SECRET_KEY,
    https_only=True,
    max_age=3600,  # 3600s = 1hr, the life of spotify access token
    same_site = "none",
    domain = ".localhost"   # THIS WAS WHAT WAS KILLING ME
)

Это предложение я видел в Интернете, но, должно быть, это было тогда, когда я обслуживал свой интерфейс на localhost, а не на 127.0.0.1.

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

Все еще нужно подтвердить, что я действительно могу использовать файлы cookie сеанса, как предполагалось, в других моих конечных точках API, но я просто наслаждаюсь моментом и побеспокоюсь об этом завтра :)

Спасибо за комментарии/предложения!

Если во время разработки ваш интерфейс использует протокол HTTP (а не HTTPS), браузеры могут блокировать файлы cookie с помощью значения Same_site="none". попробуйте установить cookie с этими параметрами:

 // Creates Secure Cookie with refresh token
    res.cookie("jwt", newRefreshToken, {
      httpOnly: true,
      secure: true,
      sameSite: "None",
      maxAge: 24 * 60 * 60 * 1000,
    });

Другие вопросы по теме

Похожие вопросы