Как использовать dj-rest-auth с большим количеством клиентов
Я бы хотел, чтобы множество различных клиентов могли получить доступ к моему сайту django (точнее, к его API), но я не уверен, как это сделать с помощью django-allauth, dj-rest-auth и simplejwt.
Мое текущее клиентское приложение использует встроенный шаблонизатор django и настроено с помощью django-allauth для социальной аутентификации (Google и т.д.). Оно работает, используя документированные рекомендации по установке.
Сейчас я хочу создать различные типы клиентов, которые не используют шаблонизатор django (например, Angular, Vue, flutter mobile и т.д.), но я запутался, как используется dj-rest-auth, чтобы он масштабировался для поддержки любого количества типов клиентов
На примере Google social sign in, когда я создаю нового клиента, я должен зарегистрировать новый redirect_uri, специфичный для этого клиента.
Чтобы проверить все это, я создал простое приложение flask с одной ссылкой, чтобы я мог получить "code/access_token" перед отправкой его в мое приложение Django. Ссылка создается следующим образом...
var codeRequestUrl =
`https://accounts.google.com/o/oauth2/v2/auth?\
scope=email&\
access_type=offline&\
include_granted_scopes=true&\
response_type=code&\
state=state_parameter_passthrough_value&\
redirect_uri=http%3A//127.0.0.1:5000/callback&\
client_id=${clientId}`;
...и код извлекается в конечной точке '/callback' во flask...
@app.route("/callback", methods=['GET'])
def redirect():
code = request.args.get('code', '')
req = requests.post('http://127.0.0.1:8000/api/dj-rest-auth/google/', data={'code':code})
return "done..."
...откуда я отправляю x-www-form-urlencoded POST запрос обратно к конечной точке dj-rest-auth, которая настроена в соответствии с документацией...
class GoogleLogin(SocialLoginView):
callback_url = 'http://127.0.0.1:5000/callback'
adapter_class = GoogleOAuth2Adapter
client_class = OAuth2Client
...
urlpatterns += [
...
path('dj-rest-auth/google/', GoogleLogin.as_view(), name='google_login'),
....
]
Django успешно возвращает access_token
, refresh_token
и некоторую информацию о вошедшем пользователе.
Но это не то, что хорошо масштабируется. Если бы я также создал клиент Angular, мне бы пришлось зарегистрировать другой обратный вызов (потому что клиент Angular будет работать на другом порту и/или адресе, и мне также понадобится другой путь, установленный в urls.py, и связать его с новым подклассом SocialLoginView, который сможет обрабатывать другой callback_url (redirect_uri).
И учитывая все это, я понятия не имею, как сделать все это с мобильным приложением flutter, которое, насколько я знаю, не имеет понятия callback_url, поэтому я не уверен, как сделать POST-запрос на .../dj-rest-auth/google/
, учитывая, что я мгновенно получу ошибку redirect_uri_mismatch
.
Может быть, я понял все наоборот, и клиент, зарегистрированный в Google, является приложением Angular, Vue, Flash и т.д.? Это означает, что каждый клиент должен обрабатывать свой собственный client_id и client_secret, что в таком случае обходит функциональность django-allauth и dj-rest-auth.
Мне кажется, что я неправильно интерпретирую это, поэтому я был бы очень признателен за некоторые предложения.
Я чувствую себя достаточно уверенно, чтобы ответить на свой вопрос.
Вкратце, да, работа с несколькими клиентами (включая сторонних) - это достаточно простой процесс. К сожалению, многие статьи в блогах и учебники, которые существуют, рассматривают ситуацию с точки зрения клиента "второй стороны", что очень запутывает ситуацию. В результате появляется множество сообщений об ошибках, связанных с redirect_uri
.
К их чести, документы Google для их примера приложения Flask были именно тем, что мне было нужно, но есть пара замечаний, которые действительно важны, и которые привели меня в замешательство.
Во-первых, и это самое важное, обратный вызов (redirect_uri
) вообще не нужен в Django. В Django требуется только что-то вроде этого.
from allauth.socialaccount.providers.oauth2.client import OAuth2Client
from allauth.socialaccount.providers.google.views import GoogleOAuth2Adapter
class GoogleLogin(SocialLoginView):
adapter_class = GoogleOAuth2Adapter
client_class = OAuth2Client
urlpatterns += [
...
path('auth/google/', GoogleLogin.as_view(), name='google_login'),
...
]
Поэтому отсутствие атрибута обратного вызова не требуется. Причина в том, что Flask (или стороннее приложение) обрабатывает всю аутентификацию на стороне Google.
Вторым наблюдением было то, что redirect_uri
в приложении Flask, похоже, должны быть одинаковыми как для шага запроса "code", так и для шага "access_token".
Вы можете увидеть это в связанном примере, где функция oauth2callback
(которая обрабатывает redirect_uri
), но я модифицировал для использования с dj-rest-auth
@app.route('/')
def index():
if 'credentials' not in flask.session:
return flask.redirect(flask.url_for('oauth2callback'))
credentials = json.loads(flask.session['credentials'])
if credentials['expires_in'] <= 0:
return flask.redirect(flask.url_for('oauth2callback'))
else:
data = {'access_token': credentials['access_token']}
headers = headers = {'Content-Type': 'application/x-www-form-urlencoded'}
r = requests.post(f'{URL_ROOT}/api/auth/google/', data=data, headers=headers)
response_json = json.loads(r.text)
access_token = response_json['access_token'] # JWT Access Token
refresh_token = response_json['refresh_token']
# Make a query to your Django website
headers = headers = {'Authorization': f'Bearer {access_token}'}
r = requests.post(f'{URL_ROOT}/api/object/{OBJECT_ID}/action/', data=data, headers=headers)
# do stuff with r
@app.route('/oauth2callback')
def oauth2callback():
if 'code' not in flask.request.args:
auth_uri = ('https://accounts.google.com/o/oauth2/v2/auth?response_type=code'
'&client_id={}&redirect_uri={}&scope={}').format(CLIENT_ID, REDIRECT_URI, SCOPE)
return flask.redirect(auth_uri)
else:
auth_code = flask.request.args.get('code')
data = {'code': auth_code,
'client_id': CLIENT_ID,
'client_secret': CLIENT_SECRET,
'redirect_uri': REDIRECT_URI,
'grant_type': 'authorization_code'}
r = requests.post('https://oauth2.googleapis.com/token', data=data)
flask.session['credentials'] = r.text # This has the access_token
return flask.redirect(flask.url_for('index'))
Подводя итог, можно сказать следующее:
- На странице index/home пользователь нажимает на html-якорь "Login", который указывает на
/login
. - Flask не имеет учетных данных, поэтому перенаправляет на
/oauth2callback
для начала аутентификации. - Сначала "код" извлекается с помощью конечной точки GET
/auth
от Googles и с помощью идентификатора клиента вашего приложения. - С помощью
redirect_uri
поток кода возвращается к самому себе, но на этот раз, зная "код", сделайте POST-запрос к конечной точке Google/token
, используя идентификатор клиента и секрет клиента вашего приложения. Опять же,redirect_uri
остается прежним (/oauth2callback
). - Теперь, когда известен токен доступа Googles, приложение Flask перенаправляет обратно на /index (хотя на данный момент это может быть где угодно). Вернувшись в /index, приложение Flask теперь имеет "access_token" Google. Используйте его для входа в созданную вами конечную точку dj-rest-auth Django.
- . Django затем вернет свои
- собственные access_token и refresh_token, так что продолжайте использовать их по мере необходимости.
Надеюсь, это поможет.
Обратите внимание, что ваше приложение flask должно быть зарегистрировано как новое веб-приложение в консоли OAuth2 Google (так что у него будет свой собственный идентификатор клиента и секрет клиента). Другими словами, не используйте то, что вы, возможно, уже создали с помощью существующей реализации Django allauth (что было моим сценарием). Каждый сторонний производитель приложений будет работать со своими собственными учетными данными OAuth2.