Вводная часть
Если при разработке сайта возникает необходимость идентифицировать пользователя, OAuth-авторизация является достаточно простым инструментом в части реализации и защищенным в части безопасности.
В публикации рассмотрена OAuth авторизация для социальных сетей Facebook, Vkontakte и аккаунтов поисковых систем Google, Yandex. Этих примеров достаточно для понимания того, что авторизация по OAuth везде одинакова, различия в реализации незначительны. А значит, полученных из публикации знаний будет достаточно для самостоятельной реализации механизма авторизации для любого другого сервиса.
Почему бы не использовать готовые библиотеки и скрипты для подключения авторизации к сайту? Потому, что:
- OAuth авторизация достаточно проста, чтобы разобраться самому и достаточно важна, чтобы не поручать сбор персональных данных посетителей своего сайта незнакомым библиотекам,
- гораздо быстрее раз и навсегда понять принципы OAuth авторизации, чем разбираться в каждой ошибке сторонней библиотеки при подключении очередного сервиса авторизации.
- Термины:
- Все сервисы (социальные сети, поисковые системы, web-сервисы, и пр.) предоставляющие OAuth авторизацию в данной публикации будем называть серверами авторизации
Чтобы реализовать на своем сайте OAuth авторизацию нужно:
- зарегистрировать свой сайт на сервере авторизации.
- разместить на своем сайте ссылку (кнопку), отправляющую пользователя на страницу сервера авторизации
- создать серверный скрипт, который будет обрабатывать результаты прохождения авторизации пользователем и, при необходимости, после успешной авторизации сохранит информацию о пользователе на сервере и перенаправит пользователя на нужную страницу сайта.
Регистрация сайта на сервере авторизации
Для регистрации сайта на сервере авторизации необходимо иметь аккаунт на нем. Например, если Вы хотите организовать для своего сайта OAuth-авторизацию пользователей через Facebook, Вы должны быть зарегистрированы на Facebook.
Ссылки на страницы регистрации нашего сайта:
- Google. На этой странице Вы регистрируете свой сайт, а по ссылке ниже можно получить параметры проекта и настроить вид окна аутентификации. Естественно в конце ссылки вместо id_project нужно указать id Вашего зарегистрированного сайта.
https://console.developers.google.com/apis/credentials?id_project
После регистрации сайта на сервере авторизации необходимо:
запомнить для последующего использования значения параметров 'client_id', и 'client_secret';
- прописать на сервере авторизации в настройках проекта URI адрес серверного скрипта которому будет передано управление после ввода пользователем пароля на сервере авторизации.
- для Google адрес прописывается во поле "Разрешенные URI перенаправления"
- Yandex - "Callback URL"
- Facebook - "URL-адрес сайта"
- ВКонтакте - "Базовый домен"
Переход с сайта на страницу авторизации
Как правило, для перехода на страницу сервера авторизации на сайт вешается кнопка, например, так:
<button click="javascript::location.href='.......'"></button>
В качестве значения ссылки указываем адрес сервера авторизации с набором параметров Адреса на страницы авторизации
- Google: https://accounts.google.com/o/oauth2/auth
- Yandex: https://oauth.yandex.ru/authorize
- Facebook: https://www.facebook.com/dialog/oauth
- ВКонтакте: https://oauth.vk.com/authorize
При переходе по указанным ссылкам необходимо передать следующие параметры:
- response_type=code - (code - значение параметра для нашего случая авторизации)
- client_id - значение присвоенное сервером авторизации при регистрации сайта
- redirect_uri - URI адрес нашего серверного скрипта, которому будет передано управление после ввода пользователем пароля на сервере авторизации. Управление передается посредством отправки сервером скрипту GET запроса.
- state - не обязательный, но полезный параметр. В него помещается произвольное значение. После успешной авторизации пользователем это же значение возвращается серверному скрипту в GET запросе. Например, в качестве значения этого параметра можно указать псевдоним сервера авторизации Это же значение сервер авторизации направит серверному скрипту. Таким образом, скрипт сможет идентифицировать с какого сервера авторизации пришел запрос.
- scope указывает серверу авторизации какие дополнительные данные пользователя мы запрашиваем для нашего сайта. Пользователь при прохождении авторизации увидит какие его данные хочет получить наш сайт, соглашается с этим или отклоняет авторизацию. Обязательные данные при прохождении автризации будут возвращаться нашему сайту всегда. К обязательным данным прежде всего относится id пользователя на сервере авторизации
Из дополнительных параметров о пользователе, скорее всего, понадобится его email. поэтому в параметре scope нужно указать
- Вконтакте - scope=email
- Google - scope=https://www.googleapis.com/auth/userinfo.email+https://www.googleapis.com/auth/userinfo.profile
- Facebook - scope=email Но здесь необходимо помнить, что на Facebook можно зарегистрироваться без указания email, через номер мобильного телефона. Поэтому email у пользователя может не быть.
- Yandex - параметр scope можно не указывать. Какая информация о пользователе необходима - задается при регистрации приложения
Параметры для доступа к другим дополнительным параметрам пользователя можно посмотреть здесь
Обработка результатов авторизации
- После успешной авторизации пользователя на сервере (отказ от авторизации и неправильный ввод логина-пароля не рассматриваем) нашему скрипту обработки (URI, указанный при регистрации сайта на сервере авторизации и как параметр при переходе пользователя на страницу авторизации) направляется GET-запрос, содержащий параметр 'code' с непустым значением.
- Получив 'code', скрипт должен добавить его в POST-запрос серверу авторизации на получения access_token. При корректных значениях параметра нашему скрипту возвращается json-объект, содержащий в том числе и нужный acess_token
- Получив acess_token скрипт отправляет GET-запрос, указав значения acess_token-а в качестве параметра. Если acess_token указан правильно - сервер авторизации возвращает json объект с данными о пользователе.
Возвращаемая информация о пользователе имеет формат JSON. Однако, структура JSON, имена ключей у каждого сервера авторизации индивидуальна.
Вот и все премудрости OAuth авторизации. Осталось только написать скрипт, обрабатывающий ответы сервера авторизации. Скрипт сделаем на Python
Реализация скрипта на python
Блок 1 Настройки серверов авторизации
service_config={
"google":{
'code':"",
'url_token':"https://accounts.google.com/o/oauth2/token",
'url_user_info':"https://www.googleapis.com/oauth2/v1/userinfo",
'client_id':"....",
'client_secret':"...",
'redirect_uri':"http://...",
'grant_type':'authorization_code'
},
"yandex":{
'code':'',
'url_token':'https://oauth.yandex.ru/token',
'url_user_info':'https://login.yandex.ru/info',
'grant_type':'authorization_code',
'client_id':'...',
'client_secret':"..."
},
"facebook":{
'code':'',
'url_token':'https://graph.facebook.com/oauth/access_token',
'url_user_info':'https://graph.facebook.com/me',
'client_id':'...',
'client_secret':'...',
'redirect_uri':"http://..."
},
"vkontakte":{
'code':'',
'url_token':'https://oauth.vk.com/access_token',
'url_user_info':'https://api.vk.com/method/users.get?fields=uid,first_name,last_name,nickname,screen_name,sex,bdate,city,country,timezone,photo',
'client_id':'...',
'client_secret':'...',
'redirect_uri':"http://..."
}
}
Для настройки ОАuth авторизации через другой сервер (не приведенный в листинге) - просто добавить соотвествующие настройки
Блок 2 Функция прохождения авторизации
Помним, что функция запускается, когда пользователь ввел логин-пароль на сервере авторизации и произошел редерикт по адресу, указанному нами при регистрации сайта. По данному адресу был направлен GET запрос, включающий параметры (агрумент вызова функции)
1 import requests
2 def start(params):
3 # ШАГ 1 проверяем, заданы ли настройки авторизации для сервиса с которого пришел GET
4 # (см значение state)
5 if not params['state'] in service_config:
6 return ''
7
8 # ШАГ 2 отсылаем запрос на получение ключа авторизации
9 headers={'Content-type':'application/x-www-form-urlencoded'}
10 service_config[params['state']]['code']=params['code']
11 r=requests.post(service_config[params['state']]['url_token'], data=service_config[params['state']], headers=headers)
12
13 # если неверно заданы параметры приложения в service_config сервер авторизации вернет словарь с ключем error
14 if 'error' in r.text:
15 return 'Параметры приложения заданы неверно'
16
17 # ШАГ 3 отсылаем запрос на получение информации о пользователе
18 if params['state']=='facebook':
19 # facebook отдает данные не json а в канонической форме - отсюда условие
20 param_token=dict([i.split('=') for i in r.text.split('&')])
21 # user_id нужно только для ВКонтакте, но мы же делаем универсальный скрипт
22 user_id=''
23 else:
24 param_token=r.json()
25 user_id=r.json().get('user_id', '')
26
27 r=requests.get(service_config[params['state']]['url_user_info'], params={'access_token':param_token['access_token'], 'user_ids':user_id, 'format':'json','oauth_token':param_token['access_token']})
28 return r.json()
Основная задача выполнена - пользователь идентифицировал себя через ОАuth, нужные данные о нем мы получили. Если не стоит задача куда-либо сохранять эти данные, то на этом можно остановиться.
Блок 3 Приведение данных о пользователе к единой форме
Нужно учитывать, что информация о пользователе, возвращаемая серверами авторизации имеет разные состав и структуру. Поэтому в общем случае необходимо писать отдельную функцию на каждый сервер авторизации для приведения информации к единому формату. Если на сайте планируется авторизовать пользователей через большое количество серверов, то можно применить такой прием.
- создаем для каждого сервера авторизации необходимую функцию, обрабатывающую переданную информацию о пользователе. В качестве обязательных параметров функции передаются:
- param_token - ответ сервера авторизации с access_token (см. выше шаг 2). VKontakte передает адрес почты пользователя в access_token
- param_info - собственно данные о пользователе,
Вот пример функций для рассматриваемых серверов авторизации
def getInfoVkontakte(param_token, param_info):
param={}
param['user_id']=str(param_token['user_id'])
param['email']=param_token['email']
param['provider']='vkontakte'
param['dop_info']=param_info
return param
def getInfoGoogle(param_token, param_info):
param={}
param['user_id']=param_info['id']
param['email']=param_info['email']
param['provider']='google'
param['dop_info']=param_info
return param
def getInfoFacebook(param_token, param_info):
param={}
param['user_id']=param_info['id']
param['email']=param_info.get('email','None')
param['provider']='facebook'
param['dop_info']=param_info
return param
def getInfoYandex(param_token, param_info):
param={}
param['user_id']=param_info['id']
param['email']=param_info['default_email']
param['provider']='yandex'
param['dop_info']=param_info
return param
- Связываем каждую функцию со значением параметра state (см. выше)
getInfo={
'vkontakte':getInfoVkontakte,
'google':getInfoGoogle,
'facebook':getInfoFacebook,
'yandex':getInfoYandex
}
- В основной функции (блок 2) добавляем строку
output=getInfo[params['state']](param_token, r.json())
return output
Теперь информация о пользователе возвращается в структурированном виде.
Ссылки
- При подготовке публикации были использованы материалы этой статьи
- Причитать по OAuth можно здесь