Как использовать 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.

Вернуться на верх