Django Social Auh и авторизация в приложениях для ВКонтакте

8 ноября 2011

Кроме авторизации в приложениях для Facebook в ходе работы над проектом была решена еще одна задача – авторизация в приложениях для ВКонтакте. Как и в случае с Facebook, у меня было сильное желание не делать два раза одну и ту же работу, а использовать одну и ту же базу пользователей и один и тот же механизм для авторизации через сайт ВКонтакте и для авторизации в iframe-приложении ВКонтакте. Это удалось решить без особых сложностей, и вот наконец пришло время сорвать покров тайны с некоторых загадочных настроек.

Особенности ВКонтакте

В отличие от Facebook, у ВКонтакте есть одна принципиальная особенность, которая несколько затрудняет решение этой задачи, а именно: вы не можете использовать одно и то же приложение для авторизации через OAuth и для авторизации в iframe-приложении. Вам необходимо зарегистрировать iframe-приложение для ВКонтакте отдельно и получить для него id и secret.

Другая особенность, которая, наоборот, упрощает работу, заключается в том, что ВКонтакте не делает POST-запрос при переходе на URL вашего приложения, а использует метод GET. То есть, минус одна заморочка с CSRF.

В целом для ВКонтакте используется тот же принцип, что и для Facebook, а именно:

  1. При обращении к view приложения происходит проверка: через какой backend авторизован пользователь;
  2. Если это ВКонтакте, то мы выбираем из базы access_token и используем его для работы с API;
  3. Если нет – мы используем полученный запрос для авторизации;
  4. Доработанный Backend для ВКонтакте разбирает запрос и производит авторизацию с учетом того, что могла использоваться как авторизация через сайт, так и авторизация через приложение.

Начнем по порядку.

Проверка авторизации

Чтобы убедиться, что пользователь нашего view авторизован через нужный нам backend, используется функция is_complete_authorization

# Checks the completeness of current user authentication; complete = logged via VKontakte backend
def is_complete_authentication(request):
    return request.user.is_authenticated() and VKontakteOAuth2Backend.__name__ in request.session.get(BACKEND_SESSION_KEY, '')

Декоратор для view приложения

Декоратор выглядит чуть проще, чем для Facebook, так как не нужно разбирать signed_request.

def vkontakte_intro(func):
    def wrapper(request, *args, **kwargs):

        # User must me logged via VKontakte backend in order to ensure we talk about the same person
        if not is_complete_authentication(request):
            try:

                social_complete(request, VKontakteOAuth2Backend.name)
            except (ValueError, AttributeError):
                pass

        # Need to re-check the completion
        if is_complete_authentication(request):
            kwargs.update({'access_token': get_access_token(request.user)})
        else:
            request.user = AnonymousUser()

        return func(request, *args, **kwargs)

    return wrapper

Назначение у функции get_access_token: вытащить из базы или из кэша access_token для указанного пользователя. Пример:

VK_AT_CACHE_PREFIX = 'VK_AT_%s'

# Returns cached access token for the user; loads it from db if needed
def get_access_token(user):
    key = VK_AT_CACHE_PREFIX % str(user.id)
    access_token = cache.get(key)

    # If cache is empty read the database
    if access_token is None:
        try:
            social_user = user.social_user if hasattr(user, 'social_user') else UserSocialAuth.objects.get(user=user.id, provider=VKontakteOAuth2Backend.name)
        except UserSocialAuth.DoesNotExist:
            return None

        if social_user.extra_data:
            access_token = social_user.extra_data.get('access_token')
            expires = social_user.extra_data.get('expires')

            cache.set(key, access_token, int(expires) if expires is not None else 0)

    return access_token

Подключение к приложению

Для работы некоторых методов API вы должны запросить у пользователя установку вашего приложения и нужные вам права. Это две разные операции, первая из которых запускается при вызове функции startConnect, а вторая – следом за ней, в функции requestRights.

{% block head %}
<script src="http://vkontakte.ru/js/api/xd_connection.js?2" type="text/javascript"></script>

{% endblock %}

{% block js %}
<script type="text/javascript">

VK.init(function() {
// any of your code here
}
    );

    function startConnect() {
        VK.callMethod('showInstallBox');
    }

    function requestRights() {
        VK.callMethod('showSettingsBox', 1 + 2); // 1+2 is just an example
    }

    function onSettingsChanged(settings) {
        window.location.reload();
    }

    $(document).ready( function(){
        VK.addCallback("onApplicationAdded", requestRights);
        VK.addCallback("onSettingsChanged", onSettingsChanged);
    });
</script>
{% endblock %}

Необходимые настройки

Для того чтобы авторизация через приложение начала работать, нужно сделать следующие настройки:

VKONTAKTE_APP_AUTH={'key':'iframe_app_secret_key', 'user_mode': 2, 'id':'iframe_app_id'}

Параметр user_mode может принимать значения 0, 1 или 2, он влияет на проверку того, подключился пользователь к вашему приложению или нет. Если поставить user_mode в 0, то никакой проверки произведено не будет и авторизация успешно пройдет в любом случае. При user_mode=1 будет проведена проверка на значение параметра is_app_user. Этот параметр приходит в GET-запросе, который присылает ВКонтакте на URL вашего приложения при начале работы с ним. Авторизация будет успешной, только если пользователь подключен к вашему приложению. Однако может случиться так, что пользователь подключился к вашему приложению позже, а запрос уже был проанализирован, is_app_user был 0, а в браузере пользователь уже давно ушел на другую страницу. Для этого используется значение 2, которое делает запрос к API и проверяет, подключил пользователь ваше приложение или нет. Лично я пользуюсь последним вариантом.

Значение key используется для проверки авторизации, id – для выполнения запросов к API. Оно имеет более высокий приоритет по отношению к VKONTAKTE_APP_ID, потому что именно для него вы получаете права от пользователя в интерфейсе.

Если вы не хотите использовать авторизацию через приложение, просто не используйте VKONTAKTE_APP_AUTH совсем.

Update

Пример использования добавлен в главную ветку 23 августа 2012.

Ответы на: Django Social Auh и авторизация в приложениях для ВКонтакте

  • anonymous пишет:

    10 ноября, 2011

    При подключении к контакту через oauth2 выдаётся 400 ошибка. В чём может быть дело?

    Firefox can't find the file at http://api.vkontakte.ru/oauth/authorize?scope=&redirect_uri=http://dev.mysite.com:8008/complete/vkontakte-oauth2/&response_type=code&client_id=myclientid.

    Собственно только домен и клиент id изменил.

    • krvss пишет:

      10 ноября, 2011

      Да много причин может быть. Я не совсем понял, что значит «только домен и client id изменил» — раньше работало, а теперь перестало?

      • anonymous пишет:

        10 ноября, 2011

        Имеется в виду в моём комментарии. Нет, раньше тоже не работало. Много причин? А какие наиболее вероятные?

  • anonymous пишет:

    10 ноября, 2011

    Прошу прощения. На заметил, что неправильный код ввёл. Спасибо за быстрый ответ.

  • hades пишет:

    18 декабря, 2011

    А почему модули для вконтактика, однокамерников и пр. не попали в основной репозиторий?

    • krvss пишет:

      18 декабря, 2011

      Основной репозиторий сделан англоязычными людьми для англоязычных сервисов, им наши сервисы совсем никак.

      • hades пишет:

        18 декабря, 2011

        Это политика автора? Или ты просто не предлагал?

        • krvss пишет:

          18 декабря, 2011

          Когда я сделал первый backend (это был ЖЖ), библиотека была почти не расширяемой, и автор специально сделал механизм подключаемых модулей. Я как-то не видел особого смысла добавлять туда наши сервисы, да в общем и сейчас не особо горю — меньше нужно согласований на какие-то изменения в основных библиотеках.

  • Igor пишет:

    28 декабря, 2011

    Взял поример с репы, доибавил вконтаките бекенд. Получаю вот такую ошибку на срвеице /done/

    __init__() takes exactly 3 arguments (1 given) (vkontakte-oauth2)

    Сранно то что Фейсбук тоже ламается.

    Посмотрел трасбек:

    File "/Users/polinom/Envs/website/lib/python2.6/site-packages/social_auth/views.py", line 66, in wrapper
    return func(request, backend, *args, **kwargs)
    File "/Users/polinom/Envs/website/lib/python2.6/site-packages/social_auth/views.py", line 95, in complete
    return complete_process(request, backend, *args, **kwargs)
    File "/Users/polinom/Envs/website/lib/python2.6/site-packages/social_auth/views.py", line 152, in complete_process
    user = auth_complete(request, backend, *args, **kwargs)
    File "/Users/polinom/Envs/website/lib/python2.6/site-packages/social_auth/views.py", line 195, in auth_complete
    return backend.auth_complete(*args, **kwargs)
    File "/Users/polinom/Envs/website/lib/python2.6/site-packages/social_auth/backends/contrib/vkontakte.py", line 147, in auth_complete
    auth_result = super(VKontakteOAuth2, self).auth_complete(*args, **kwargs)
    File "/Users/polinom/Envs/website/lib/python2.6/site-packages/social_auth/backends/__init__.py", line 591, in auth_complete
    return authenticate(*args, **kwargs)
    File "/Users/polinom/Envs/website/lib/python2.6/site-packages/django/contrib/auth/__init__.py", line 53, in authenticate
    for backend in get_backends():
    File "/Users/polinom/Envs/website/lib/python2.6/site-packages/django/contrib/auth/__init__.py", line 44, in get_backends
    backends.append(load_backend(backend_path))
    File "/Users/polinom/Envs/website/lib/python2.6/site-packages/django/contrib/auth/__init__.py", line 38, in load_backend
    return cls()
    TypeError: __init__() takes exactly 3 arguments (1 given)

    Есть идеи в чем может быть проблема?

    • krvss пишет:

      28 декабря, 2011

      Что значит «добавил вконтакте бэкэнд»? Только что проверил — ВКонтакте и Facebook работают нормально в примере из репозитария. Видимо что-то в настройках.
      Надо проверить:

      FACEBOOK_APP_ID
      FACEBOOK_API_SECRET
      VKONTAKTE_APP_ID
      VKONTAKTE_APP_SECRET

      Вот кроме них какие значения у меня в local_settings:

      SOCIAL_AUTH_CREATE_USERS = True
      SOCIAL_AUTH_FORCE_RANDOM_USERNAME = False
      SOCIAL_AUTH_DEFAULT_USERNAME = ‘socialauth_user’
      SOCIAL_AUTH_COMPLETE_URL_NAME = ‘socialauth_complete’
      LOGIN_ERROR_URL = ‘/error/’
      LOGIN_REDIRECT_URL = ‘/done/’
      SOCIAL_AUTH_ERROR_KEY = ‘social_auth_error’

      • Igor пишет:

        28 декабря, 2011

        В AUTHENTICATION_BACKENDS не правильно бекенд прописал.
        В место ‘social_auth.backends.contrib.vkontakte.VKontakteOAuth2Backend’,
        написал ‘social_auth.backends.contrib.vkontakte.VKontakteOAuth2’,

        Так что все гуд.
        Только Гугл так и не роботает.
        Когда гугл редиректит меня обратно бросает на LOGIN_ERROR_URL,

        • krvss пишет:

          29 декабря, 2011

          Ага. А который из именно из Гуглов?

          • Igor пишет:

            29 декабря, 2011

            google-oauth2

        • krvss пишет:

          30 декабря, 2011

          Да, у меня тоже самое — ошибка 400. Проблема в том, что я не пользуюсь Google OAuth 2 🙂 Но я посмотрю в праздники где может быть проблема. Отпишусь здесь.

          • krvss пишет:

            4 января, 2012

            Проверил сегодня — оказалось был сам неправ, потерял последний символ в CLIENT SECRET. Как только вписал правильные параметры — все заработало.

  • Андрей пишет:

    11 января, 2012

    Есть ли способ избавиться от лишней страницы при обычной авторизации через ВКонтакте (без приложения)? Имеется ввиду страница из example.

    В суть работы oauth2 не вникал, но если поставить код из vkontakte.html к остальным ссылкам авторизации, то выдается Authentication cancelled (vkontakte-oauth2). Не хватает token-а?

    • krvss пишет:

      11 января, 2012

      vkontakte.html это для OpenAPI авторизации, не для OAuth. А токен нужен по любому 🙂

      • Андрей пишет:

        17 января, 2012

        Да, точно. Не заметил, что есть два способа авторизации через вконтакт. Спасибо!

  • aklyuchev пишет:

    23 июля, 2012

    Всем привет!
    Пробую авторизоватьcя через vkontakte
    после вызова функции VK.auth.login из vkontakte.html
    вызывается функци vkontakte.py:auth_complete:
    вываливаеется все вот тут
    if not ‘id’ in self.request.GET or \
    not app_cookie in self.request.COOKIES:
    raise ValueError(‘VKontakte authentication is not completed’)
    Вывел print self.request.COOKIES :
    {‘csrftoken’: ‘YyoN77mUYColn1wbtJ1LFlVRPL7faTTt’}
    Помогите пожалуйста разобраться

    • krvss пишет:

      23 июля, 2012

      Привет! Я в запарке, смогу посмотреть ближе к середине-концу недели. Пример кстати работает?

  • aklyuchev пишет:

    24 июля, 2012

    Это из примера как раз

  • aklyuchev пишет:

    24 июля, 2012

    ззапрос получается такой http://192.168.1.3:8000/complete/vkontakte/?first_name=%D0%90%D0%BB%D0%B5%D0%BA%D1%81%D0%B0%D0%BD%D0%B4%D1%80&last_name=%D0%9A%D0%BB%D1%8E%D1%87%D0%B5%D0%B2&nickname=&id=41424480
    Т.е данные из контакта валидные получил но при этом cookies почему то не содержит
    это строчки
    ‘vk_app_’ + self.APP_ID

    • krvss пишет:

      27 июля, 2012

      Полез проверять авторизацию через OpenAPI и оказалось, что она была сломана — правда не в том месте, а дальше. Обновил у себя, сделал pull request в главный бранч. Сейчас пример работает корректно.

      • nonamenix пишет:

        18 октября, 2012

        вечер добрый,
        что-то ошибка с записью app_cookie в cookies так до сих пор и выскакивает, не подскажите как пофиксить можно?

        • krvss пишет:

          22 октября, 2012

          если бы у меня выскакивало тоже, то может и посказал бы 🙂 напомни пожалуйста, как ее можно воспроизвести.

  • aklyuchev пишет:

    27 июля, 2012

    обновился отсюда
    https://github.com/krvss/django-social-auth.git
    таже самая проблема с куки
    Видимо это локальная проблема.
    Что делать?
    Запускаюсь на локальной машине без веб сервера
    из каталока exmaple
    python manage.py runserver 0.0.0.0:8000

    • krvss пишет:

      28 июля, 2012

      Не, тут надо запускаться на том домене, который прописан в настройках приложения ВКонтакте. То есть, если там указан mysite.net, то в etc/hosts нужно вписать 127.0.0.1 mysite.net и запускать sudo python manage.py runserver mysite.net:80

  • aklyuchev пишет:

    28 июля, 2012

    Дак так и есть :
    в настройка приложения вконтакте
    адрес сайте и базовый домен — http://192.168.1.3 — мой айпишник
    и запускаюсь как sudo python manage.py runserver 0.0.0.0:80
    те запросы к моему сервесу возможны через адрес http://192.168.1.3

    • krvss пишет:

      29 июля, 2012

      Даже не знаю 🙂 А приложение в браузере ты на каком адресе открываешь — на 192.168.1.3? И вообще, чем отличается твой вариант от примера? Если ничем, то можем сравнить настройки приложения ВКонтакте.

  • aklyuchev пишет:

    1 августа, 2012

    В сеттингс добавил
    VKONTAKTE_APP_ID = » — APP_ID моего VK приложения
    VKONTAKTE_API_SECRET = » — Secret Key моего приложения

    SOCIAL_AUTH_CREATE_USERS = True
    SOCIAL_AUTH_FORCE_RANDOM_USERNAME = False
    SOCIAL_AUTH_DEFAULT_USERNAME = ‘socialauth_user’
    SOCIAL_AUTH_COMPLETE_URL_NAME = ‘socialauth_complete’
    LOGIN_ERROR_URL = ‘/error/’
    LOGIN_REDIRECT_URL = ‘/done/’
    SOCIAL_AUTH_ERROR_KEY = ‘social_auth_error’

    + заменил

    DATABASES = {

    }

    на свою базу.

    А в настройка VK приложения же посути Задается пользователем только адрес сайта и базовый домен — у меня в обоих полях прописано http://192.168.1.3

  • aklyuchev пишет:

    1 августа, 2012

    Может вечером в скайпе спишемся ? Думаю так будет проще разобраться
    Буду очень признателен

    • krvss пишет:

      1 августа, 2012

      Насчет скайпа я не против, но сегодня и возможно завтра меня вечером в сети не будет. Напишу, когда. Какой твой часовой пояс — Москва?

  • aklyuchev пишет:

    1 августа, 2012

    угу, я из Питера

  • Vceslava пишет:

    7 декабря, 2012

    Здравствуйте, у меня к Вам вопрос. Когда я использовала регистрацию на вконтакте через обычное приложение, то никаких проблем не возникало. Однако, когда я пробую сделать регистрацию через iframe приложение, у меня возникает следующая ошибка: Incorrect authentication service «vkontakte-oauth2»
    В настройках, я добавила нужные поля, но проблема осталась.
    AUTHENTICATION_BACKENDS = (

    ‘social_auth.backends.contrib.vkontakte.VKontakteOAuth2Backend’,

    ‘django.contrib.auth.backends.ModelBackend’,
    )
    VKONTAKTE_APP_ID=’…’
    VKONTAKTE_APP_SECRET=’…’

    VKONTAKTE_APP_AUTH = {
    ‘key’: ‘…’,
    ‘user_mode’: 2,
    ‘id’: ‘…’
    }
    Подскажите пожалуйста, что делать.

    • krvss пишет:

      7 декабря, 2012

      Вообще причин может быть масса. Сначала нужно убедиться в двух вещах: 1. что вы запускаете приложение не на localhost, а от имени того сайта, который указан в приложении; 2. что вы используете не то же самое приложение, которое использовали для обычной авторизации — для iframe тип приложения в ВКонтакте другой, точно не помню какой именно, но не подключенный сайт как для обычного варианта.

      • Vceslava пишет:

        7 декабря, 2012

        Я точно не использую локалхост, так как работаю через сервер, и я указываю данные о новом приложении, плюс добавила VKONTAKTE_APP_AUTH. причем регистрация через твиттер и фейсбук работает. а вот контакт…

        • krvss пишет:

          10 декабря, 2012

          Ну, регистрация через Twitter и Facebook (если это обыный Facebook, а не iframe-приложение для него) работают совсем по другой схеме. У Вконтакте вообще уникальная в этом случае ситуация, в том смысле, что нужно иметь 2 разных приложения — одно для oauth, а второе для iFrame.

          Если проблема возникает, и локальный хост не используется, то проблема в настройках. Я бы посоветовал проверить значения настроек VK_APP_ID, VK_API_SECRET и убедиться что правильно указаны VKONTAKTE_APP_AUTH={‘key’:’iframe_app_key’, ‘user_mode’: 2, ‘id’:’iframe_app_id’} и использовать для проверки пример из самого Django-Social-Auth, чтобы гарантированно использовать рабочий код.

Comments closed