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

23 августа 2012

Пример добавлен в мой fork и в главную ветку вот в этом pull request.

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.