Django-rest-auth + Allauth Twitter. Ошибка 89. Неверный или просроченный токен
Я использую Django с Allauth + REST-Auth для входа в социальную сеть SPA и успешно настроил авторизацию на Facebook, VK и Google, но столкнулся с проблемой при добавлении Twitter. Он заканчивается {"code":89,"message":"Неверный токен или токен с истекшим сроком действия."} Похоже, я что-то упустил, потому что стандартный вход в Twitter работает как надо
Вот мои попытки:
Прежде всего, я установил конечную точку входа в Twitter, как описано в документе:
class TwitterLogin(SocialLoginView):
serializer_class = TwitterLoginSerializer
adapter_class = CustomTwitterOAuthAdapter
Он включает в себя метод post, ожидающий access_token и token_secret. Итак, представление перенаправления было создано для получения перенаправления из твиттера, полного входа в систему и установки внутреннего токена django для браузера localStorage с помощью шаблона рендера (с парой строк JS):
class TwitterReceiveView(APIView):
def get(self, request, *args, **kwargs):
access_token = request.query_params.get('oauth_token')
token_secret = request.query_params.get('oauth_verifier')
params = {'access_token': access_token,
'token_secret': token_secret}
try:
result = requests.post(settings.DOMAIN + reverse('tw_login'), data=params).text
result = json.loads(result)
except (requests.HTTPError, json.decoder.JSONDecodeError):
result = {}
access_token = result.get('access_token')
context = {'access_token': access_token}
return render(request, 'account/local_storage_setter.html',
context, content_type='text/html')
Должен отметить, что я попробовал два метода для запуска процесса (получить начальный токен) 1. Использовал стандартный URL-адрес allauth http://0.0.0.0:8080/accounts/twitter/login2. Создал другое представление (используя lib python oauth2), которое могло использовать из SPA:
class TwitterGetToken(APIView):
def get(self, request, *args, **kwargs):
request_token_url = 'https://api.twitter.com/oauth/request_token'
authorize_url = 'https://api.twitter.com/oauth/authorize'
app = SocialApp.objects.filter(name='Twitter').first()
if app and app.client_id and app.secret:
consumer = oauth.Consumer(app.client_id, app.secret)
client = oauth.Client(consumer)
resp, content = client.request(request_token_url, "GET")
if resp['status'] != '200':
raise Exception("Invalid response {}".format(resp['status']))
request_token = dict(urllib.parse.parse_qsl(content.decode("utf-8")))
twitter_authorize_url = "{0}?oauth_token={1}"\
.format(authorize_url, request_token['oauth_token'])
return redirect(twitter_authorize_url)
raise Exception("Twitter app is not set up")
Я даже пытался написать метод get для FacebookLoginView и передать ему обратный вызов Twitter
class TwitterLogin(SocialLoginView):
serializer_class = TwitterLoginSerializer
adapter_class = TwitterOAuthAdapter
def get(self, request, *args, **kwargs):
data = {
'access_token': request.query_params.get('oauth_token'),
'token_secret': request.query_params.get('oauth_verifier')
}
self.request = request
self.serializer = self.get_serializer(data=data,
context={'request': request})
self.serializer.is_valid(raise_exception=True)
self.login()
return self.get_response()
Все методы привели меня к упомянутой ошибке. Не могли бы вы, пожалуйста, посоветовать что-нибудь в моем случае. Заранее спасибо!
ОБНОВЛЕНИЕ: Вот как это работает для меня:
import json
import requests
import urllib.parse
import oauth2 as oauth
from requests_oauthlib import OAuth1Session
from django.urls import reverse
from django.conf import settings
from django.shortcuts import redirect, render
from rest_framework.views import APIView
from allauth.socialaccount.models import SocialApp
from allauth.socialaccount.providers.twitter.views import TwitterOAuthAdapter, TwitterAPI
from rest_auth.social_serializers import TwitterLoginSerializer
from rest_auth.registration.views import SocialLoginView
class TwitterGetToken(APIView):
'''
Initiates Twitter login process
Requests initial token and redirects user to Twitter
'''
def get(self, request, *args, **kwargs):
request_token_url = 'https://api.twitter.com/oauth/request_token'
authorize_url = 'https://api.twitter.com/oauth/authorize'
app = SocialApp.objects.filter(name='Twitter').first()
if app and app.client_id and app.secret:
consumer = oauth.Consumer(app.client_id, app.secret)
client = oauth.Client(consumer)
resp, content = client.request(request_token_url, "GET")
if resp['status'] != '200':
raise Exception("Invalid response {}".format(resp['status']))
request_token = dict(urllib.parse.parse_qsl(content.decode("utf-8")))
twitter_authorize_url = "{0}?oauth_token={1}"\
.format(authorize_url, request_token['oauth_token'])
return redirect(twitter_authorize_url)
raise Exception("Twitter app is not set up")
class TwitterLogin(SocialLoginView):
'''
Takes the final twitter access token, secret
Returns inner django Token
'''
serializer_class = TwitterLoginSerializer
adapter_class = TwitterOAuthAdapter
class TwitterReceiveView(APIView):
'''
Receives Twitter redirect,
Requests access token
Uses TwitterLogin to logn and get django Token
Renders template with JS code which sets django Token to localStorage and redirects to SPA login page
'''
def get(self, request, *args, **kwargs):
access_token_url = 'https://api.twitter.com/oauth/access_token'
callback_uri = settings.DOMAIN + '/accounts/twitter/login/callback/'
app = SocialApp.objects.filter(name='Twitter').first()
client_key = app.client_id
client_secret = app.secret
oauth_session = OAuth1Session(client_key,
client_secret=client_secret,
callback_uri=callback_uri)
redirect_response = request.get_full_path()
oauth_session.parse_authorization_response(redirect_response)
token = oauth_session.fetch_access_token(access_token_url)
params = {'access_token': token['oauth_token'],
'token_secret': token['oauth_token_secret']}
try:
result = requests.post(settings.DOMAIN + reverse('tw_login'),
data=params).text
result = json.loads(result)
except (requests.HTTPError, json.decoder.JSONDecodeError):
result = {}
access_token = result.get('access_token')
context = {'access_token': access_token,
'domain': settings.DOMAIN}
return render(request, 'account/local_storage_setter.html',
context, content_type='text/html')
0 ответов
Отличный код, спасибо за публикацию!
Я хотел бы добавить, однако, что аутентификация пользователя может быть выполнена непосредственно из внешнего интерфейса, и, учитывая, что вы пишете SPA, кажется логичным сделать это вместо того, чтобы перенаправлять ваш в бэкэнд (что нарушает представление RESTful), чтобы авторизоваться, а затем отправить ответ.
Я использовал следующий вспомогательный класс JS, основанный на vue-authenticate. Чтобы сделать всплывающее окно и получить информацию из URL обратного вызова
export default class OAuthPopup {
constructor(url, name, redirectURI) {
this.popup = null
this.url = url
this.name = name
this.redirectURI = redirectURI
}
open() {
try {
this.popup = window.open(this.url, this.name)
if (this.popup && this.popup.focus) {
this.popup.focus()
}
return this.pooling()
} catch(e) {
console.log(e)
}
}
pooling() {
return new Promise((resolve, reject) => {
let poolingInterval = setInterval(() => {
if (!this.popup || this.popup.closed || this.popup.closed === undefined) {
clearInterval(poolingInterval)
poolingInterval = null
reject(new Error('Auth popup window closed'))
}
try {
var popupLocation = this.popup.location.origin + this.popup.location.pathname
if (popupLocation == this.redirectURI) {
if (this.popup.location.search || this.popup.location.hash ) {
const urlParams = new URLSearchParams(this.popup.location.search);
var params = {
oauth_token: urlParams.get('oauth_token'),
oauth_verifier: urlParams.get('oauth_verifier'),
url: this.popup.location.href
}
if (params.error) {
reject(new Error(params.error));
} else {
resolve(params);
}
} else {
reject(new Error('OAuth redirect has occurred but no query or hash parameters were found.'))
}
clearInterval(poolingInterval)
poolingInterval = null
this.popup.close()
}
} catch(e) {
// Ignore DOMException: Blocked a frame with origin from accessing a cross-origin frame.
}
}, 250)
})
}
}
Методология, которой я придерживался, похожа на вашу:
- Я делаю GET-запрос к TwitterGetToken и получаю URL-адрес авторизации Twitter в ответ
- Я использую URL в ответе от моего внешнего интерфейса, чтобы открыть всплывающее окно, которое позволяет пользователю авторизоваться
- Я делаю POST-запрос к TwitterReceiveView и прикрепляю URL ответа после твиттер-аутентификации
все остальное просто встает на свои места и пользователю возвращается ключ доступа.
В любом случае, спасибо, я возился с множеством библиотек в js и python, но это был просто лучший способ сделать что-то