Социальная аутентификация (DRF + Djoser), "non_field_errors": ["Предоставлено неверное состояние."] после POST-запроса с состоянием и кодом
Я следую этому видеоуроку и пытаюсь реализовать социальную аутентификацию Google с помощью DRF, djoser и React.
Я набираю в браузере следующее (GET-запрос):
http://localhost:8000/auth/o/google-oauth2/?redirect_uri=http://localhost:8000
Я получаю вот такой ответ (я не уверен, что этот url безопасен для совместного использования, но в любом случае я его немного изменил)
{ "authorization_url": "https://accounts.google.com/o/oauth2/auth?client_id=836198290956-fe0ilujf6e23l882oumgkufi8qm6fg3m.apps.googleusercontent.com&redirect_uri=http://localhost:8000&state=eNwMFCmEplYgbUTTP9nnrQ6MduAPxzDY&response_type=code&scope=https://www.googleapis.com/auth/userinfo.email+https://www.googleapis.com/auth/userinfo.profile+openid+openid+email+profile" }
Я ввожу этот ответ в браузер, который перенаправляет меня на страницу входа в google. Затем я выбираю свою учетную запись, после чего нажимаю продолжить. Теперь я перенаправлен на localhost:8000 с таким url
http://localhost:8000/?state=eNwMFCmEplYgbUTTP9nnrQ6MduAPxzDY&code=4%2F0AcvDMrB6f3ZQuTD563Vxriu2n0VHmLEOHnDRqC6jD5BRm068jj2tyExxfZZJDFLAtcwYLg&scope=email+profile+openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email&authuser=0&hd=circle.help&prompt=consent
Моя проблема проявляется здесь. Я беру параметры state и code из этого url, и делаю POST запрос с помощью Postman (без тела, только url, но я также установил Content-Type заголовок на application/x-www-form-urlencoded) к этому url:
localhost:8000/auth/o/google-oauth2/?state=eNwMFCmEplYgbUTTP9nnrQ6MduAPxzDY&code=4%2F0AcvDMrB6f3ZQuTD563Vxriu2n0VHmLEOHnDRqC6jD5BRm068jj2tyExxfZZJDFLAtcwYLg
Но в ответ я получаю следующее ( Вот моя проблема):
{
"non_field_errors": [
"Invalid state has been provided."
]
}
Я попытался отладить его и обнаружил, что причина кроется в этом модуле:
venv/lib/python3.11/site-packages/social_core/backends/oauth.py
class OAuthAuth(BaseAuth):
...
# Other methods
def validate_state(self):
"""Validate state value. Raises exception on error, returns state
value if valid."""
if not self.STATE_PARAMETER and not self.REDIRECT_STATE:
return None
state = self.get_session_state()
request_state = self.get_request_state()
if not request_state:
raise AuthMissingParameter(self, "state")
elif not state:
raise AuthStateMissing(self, "state")
elif not constant_time_compare(request_state, state):
raise AuthStateForbidden(self)
else:
return state
...
# Other methods
В моем случае в методе OAuthAuth.validate_state() переменная state отличается от переменной request_state, а request_state (из OAuthAuth.validate_state()) совпадает с state (из урлов)
.
в обоих приведенных выше урлах, но state (из OAuthAuth.validate_state()) совершенно другой. Я не могу понять, откуда это взялось, почему
self.get_request_state()
возвращает другое состояние, чем в url? Возможно, я делаю что-то не так, и мне нужно передать какой-то cookie в Postman?
Вот список моих установленных пакетов:
asgiref==3.8.1
certifi==2024.7.4
cffi==1.16.0
charset-normalizer==3.3.2
cryptography==42.0.8
defusedxml==0.8.0rc2
Django==5.0.7
django-filter==24.2
django-templated-mail==1.1.1
djangorestframework==3.15.2
djangorestframework-simplejwt==5.3.1
djoser==2.2.3
idna==3.7
Markdown==3.6
oauthlib==3.2.2
psycopg==3.2.1
psycopg2-binary==2.9.9
pycparser==2.22
PyJWT==2.8.0
python3-openid==3.2.0
requests==2.32.3
requests-oauthlib==2.0.0
social-auth-app-django==5.4.2
social-auth-core==4.5.4
sqlparse==0.5.1
typing_extensions==4.12.2
urllib3==2.2.2
Вот мой файл settings.py
Я нашел кучу похожих вопросов, но они не полностью объясняли, что является причиной этой ошибки.
Проблема, с которой вы столкнулись, заключается в том, что состояние не сохраняется при выполнении редиректов. Состояние сохраняется в session
под ключом f'_state_{self.name}_{state}'
. Значение self.name
не имеет значения. А вот значение state
имеет. Значение state
возвращается Google в качестве аргументов запроса при перенаправлении после проверки ваших учетных данных. Обычно оно хранится в oauth_token
arg. Это значение сравнивается с тем, которое находится в сессии или кэше.
Так что, по сути, вы должны сохранять session
при выполнении вызовов бэкенда. Если вы используете браузер, лучше всего использовать режим инкогнито. Если вы также хотите использовать postman, убедитесь, что сессия копируется в запрос перед его выполнением.