У меня есть полнофункциональное приложение, состоящее из интерфейса 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, касающихся этой проблемы, и думаю, что реализовал требования следующим образом:
127.0.0.1:3000
)allow_credentials=True
на бэкэндеwithCredentials: true
ИНСТРУМЕНТЫ БРАУЗЕРА:
Я также проверил вкладку «Сеть» в инструментах браузера и увидел, что мой сеанс правильно записывается после моего POST-запроса на вход/аутентификацию:
Но любые последующие запросы к моему серверу не содержат этот файл cookie сеанса, поэтому они сразу же не проходят проверку подлинности:
Разве мой файл cookie сеанса не должен отображаться в этих заголовках запросов, чтобы мой сервер мог их получить? Что я делаю не так?
Спасибо!
Вы можете проверить, действительно ли браузер сохранил файл cookie. Он находится на вкладке «Хранилище» инструментов разработки, но из-за разных доменов вам, возможно, придется сначала «открыть» localhost:8000
.
@jub0bs localhost
часто имеет особые правила, Developer.mozilla.org/en-US/docs/Web/HTTP/Headers/…
Также имеет значение, насколько сложна ваша реальная страница, например, установка файлов cookie изнутри <iframe>
может быть заблокирована в зависимости от браузера и/или его настроек (вы можете добиться успеха с Firefox, но если вы когда-нибудь встретите пользователя Safari, он просто сломается). .
на снимке экрана заголовка запроса вы можете видеть, что httponly
и secure
установлены, поэтому браузер должен принимать файлы cookie... но когда я проверяю «Cookies» на вкладке хранилища инструментов разработки, их нет. Так что это явно не принимается. Что я делаю не так? Я четко следую всей документации FastAPI, MDN CORS и т. д.
@tevemadar Спасибо; Я этого не знал. Очень интересно. Я прочитал все RFC, связанные с файлами cookie, но не помню, чтобы читал в них об этом исключении. Кажется, это отклонение от RFC, которые браузеры решили реализовать. По моим тестам, 127.0.0.1
тоже работает. Интересно, все ли петлевые IP-адреса работают...
@tevemadar Чего бы это ни стоило, я реализовал подобные исключения для localhost
и IP-адресов обратной связи в моей библиотеке промежуточного программного обеспечения CORS для Go.
@tprebenda, если вы не совсем против Chrome, вы можете попробовать эту штуку. Кроме того, у него есть отдельное представление chrome://settings/content/all
, где вы можете увидеть, есть ли файл cookie для localhost:8000
или нет (хотя в этом представлении вы не можете увидеть его содержимое, только если оно существует).
Помимо ссылки, приведенной выше, вы также можете найти этот ответ и этот ответ также полезными.
Должно быть, я не отслеживал, какое возможное решение я пробовал... Я думаю, что, возможно, я их смешал и смешал, в результате чего файлы 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,
});
Ни один современный браузер не будет принимать попытки небезопасного веб-источника, например
https://127.0.0.1:8000
, создать файл cookie с пометкойSecure
; браузеры молча отвергают такие попытки. Вот почему, несмотря на наличие заголовкаSet-Cookie
, рассматриваемый файл cookie не создается в браузере.