Не так давно я написал статью «OAuth v1«, в которой рассказывал об основах работы с OAuth с помощью одноименного модуля фреймворка Kohana. Теперь модуль расширился функционально, и я, как и обещал, рассказываю о работе модуля OAuth со второй версией этой спецификации.
Опять теория…
Как обычно, сперва немного расскажу об основных принципах. Основные термины те же, что и в первой части — провайдер, консумер (клиент), токен. Красивой картинки в качестве иллюстрации я не нашел, поэтому придется довольствоваться схемкой из замечательной статьи с Хабра (обязательно читать, если еще не успели!):
Как мы видим, количество шагов заметно уменьшилось. Вообще, одной из основных целей разработки новой версии стандарта стало упрощение алгоритма, избавление от особо сложных и трудоемких его частей, например от «подписывания» запросов (signature). Давайте рассмотрим все шаги по очереди. На данный момент модуль имеет только драйвер для Github, поэтому в качестве эталонного провайдера я буду использовать его.
0. Как обычно, сперва приложение надо зарегистрировать. Указываем название, адрес сайта и callback, т.е. адрес, по которому Github направит пользователя обратно (шаг №3). Как результат получаем client_id
и client_secret
, в общем тут все как в первой версии.
Точнее, есть один нюанс. По непонятной причине для второй версии модуль OAuth использует публичный ключ ‘id‘ в конфиге, вместо старого ‘key‘.
1. Редирект на страницу авторизации. Github просит передать в GET-строке запроса параметры client_id
(публичный ключ приложения) и redirect_uri
(т.е. callback, который мы указывали при регистрации приложения).
Github не самый строгий провайдер в части исполнения стандарта (возможно, тут сказывается «черновой» статус документа). К примеру, спецификация требует передавать еще и
response_type=code
. У других провайдеров также могут быть свои особенности, Google для своей реализации OAuth v2 хочет еще иscope
, хотя тот указан как необязательный параметр.
2. Пользователь видит в браузере страницу провайдера с предложением разрешить вход на сайт с помощью своей учетной записи.
3. Пользователь согласился, и был перенаправлен обратно на наш сайт, на ту самую страницу-callback (она же была указана в параметре redirect_uri
). В этом случае GET-строка этого адреса будет содержать параметр code
— это наш request token (временный токен запроса, если говорить в терминах OAuth v1).
4. Далее необходимо закончить авторизацию, обратившись за токеном доступа (access_token). Для этого генерируется POST-запрос с параметрами grant_type
(должен быть «authorization_code«), client_id
(публичный ключ приложения), client_secret
(секретный ключ приложения), code
(полученный request token
) и все тот же redirect_uri
.
Что интересно, я так и не нашел в спецификации требования обязательно передавать
client_secret
, но что Github, что Google (да и Mail.ru вроде тоже) его ждут. Ну, нам не жалко.
Этот запрос не требует от пользователя подтверждения, поэтому проходит в фоновом режиме (curl-запрос).
5. В качестве ответа приходят данные, содержащие access_token
— искомый токен доступа. Кроме него, там могут быть еще время жизни (expires_in
) и refresh_token
(токен, используемый в случае просроченности access token
‘а, т.е. когда у него заканчивается срок жизни).
И тут есть расхождения. Github возвращает данные в виде GET-строки (
access_token=0123456789abcdef
), а
в спецификации явно указано, что ожидается формат JSON (так возвращает данные Google).
Собственно на этом все. Далее токет можно использовать для обращения к ресурсам пользователя через авторизованный аккаунт. Схема стала намного проще в понимании, это приятно.
Как и в случае с OAuth v1, я не буду рассматривать тут вопросы обработки ошибочных кодов или обновления токенов в связи с их просроченностью. Полностью функционал можно будет увидеть на примере проекта kohana-world, который я планирую сопровождать статьями в блоге.
Практическая часть
Я использую ветку 3.1/develop модуля, т.к. там есть (или должны появиться в ближайшее время) несколько достаточно важных коммитов, препятствовавших нормальной работе с OAuth.
Для начала сохраняем в конфиге данные своего OAuth-проекта:
... // Подсмотреть свои ключи можно тут - https://github.com/account/applications/ 'github' => array( 'id' => 'публичный ключ', // помним, что тут ключ id вместо key! 'secret' => 'секретный ключ', ), ... |
Далее создаем абстрактный контроллер для работы с OAuth v2:
abstract class Controller_OAuth2 extends Controller { /** * @var OAuth2 */ protected $_oauth; /** * @var OAuth_Token */ protected $_token; /** * @var OAuth2_Provider */ protected $_provider; /** * @var OAuth_Client */ protected $_consumer; protected $_cookie; public $name; public function before() { parent::before(); $this->_oauth = new OAuth2; // создаем объект консумера, загружая в него настройки приложения из конфига $this->_consumer = OAuth2_Client::factory(Kohana::config('oauth.'.$this->name)); $this->_cookie = 'oauth_cookie_'.$this->name; // создаем объект провайдера $this->_provider = $this->_oauth->provider($this->name); if ($token = Cookie::get($this->_cookie)) { // а вдруг мы уже имеем в куках временный токен (code) $this->_token = unserialize($token); } } public function action_login() { // подготавливаем callback для передачи провайдеру $callback = $this->request->url(array('action' => 'complete'), Request::initial()->protocol()); $this->_consumer->callback($callback); // редиректим пользователя на страницу провайдера $this->request->redirect($this->_provider->authorize_url($this->_consumer)); } public function action_complete() { // мы должны получить code в GET-строке $code = $this->request->query('code'); if ( ! $code) { // по идее надо проверять наличие кодов ошибок и т.д. return; } // меняем код временного токена на токен доступа. Используется curl $this->_token = $this->_provider->access_token($this->_consumer, $code); // сохраняем токен в куке, далее можно его использовать для получения данных пользователя Cookie::set($this->_cookie, serialize($this->_token)); // перенаправляем куда-нибудь - аутентификация закончена, токен получен $this->request->redirect('/'); } } |
Основная часть написана. Далее для провайдера Github надо создать свой контроллер:
class Controller_OAuth_Github extends Controller_OAuth2 { public $name = 'github'; } |
Вот и все! Поскольку Github не требует каких-либо дополнительных телодвижений, в контроллере нам остается только указать имя провайдера, чтобы загрузить соответствующий драйвер OAuth.
Примерный код для обращения к ресурсам аккаунта через полученный токен:
// Создается объект OAuth2_Request_Credentials для выполнения GET-запроса к адресу $resource_url $request = OAuth2_Request::factory('credentials', 'GET', $resource_url, array( 'oauth_consumer_key' => $client_id, // публичный ключ приложения 'oauth_token' => $token, // наш свеженький access_token )); // получаем ответ от провайдера, дальше надо его разбирать и использовать в своих целях $response = $request->execute(); |
В общем, объемы кода не слишком большие, наиболее сложные блоки спрятаны внутри модуля OAuth, так что на остается только аккуратно собирать параметры запросов и подкладывать их в нужные вызовы. Что касается драйверов, то в ближайшее время я планирую добавить как минимум драйвер для Google (сейчас я его тестирую). На самом деле драйверы добавляются достаточно легко, обычно они содержат только ссылки на соответствующие точки доступа авторизации (т.н. endpoint‘ы). Вот так выглядит драйвер Github, к примеру:
abstract class Kohana_OAuth2_Provider_Github extends OAuth2_Provider { public $name = 'github'; public function url_authorize() { return 'https://github.com/login/oauth/authorize'; } public function url_access_token() { return 'https://github.com/login/oauth/access_token'; } } |
Дополнительные ссылки
- Собственно последняя на данный момент версия черновика стандарта OAuth v2.
- Короткий, но очень полезный guide по работе OAuth v2 в Github.
- Уже упомянутая мной в тексте статья на Хабре. Заодно и путеводитель по OAuth применительно к Mail.ru.
- Использование OAuth v2 в сервисах Google
Спасибо! Прочитал хорошую «вводную» статью в тему!
@biakaveron, не встречал модулей для Kohana, которые позволяли бы делать кроспостинг в LJ и LI блоги?
@stalker
Честно говоря, не интересовался. Теоретически, если есть возможность осуществлять это через OAuth, проблем быть не должно.
А где вообще модули для Kohana найти можно? Есть какой-то репозиторий?
Я на гитхабе только находил сборку из кучи модулей. Типо все проекты на гитхабе как-то связанные с Kohana.
1. Поиск через Github по слову Kohana.
2. Через упомянутую выше сборку модулей (по сути это что-то вроде готового результата поиска из п1).
3. С помощью сайтов типа http://kohana-modules.com.
Спасибо! Отличная статья, помогла мне разобраться с OAuth.
Но вот у меня проблема. Kohana выдает ошибку когда провайдер выдает ответ в формате JSON. Подскажите, пожалуйста, как это исправить?
Какая ошибка, какой провайдер? В общем, давайте подробности
Ошибка появляется при использовании, провайдеров МойМир и ВКонтакте.
Ошибка появляется в функции parse_params(), Undefined offset: 1. Как я понимаю она, пытается обработать урл и получить из него GET параметры, но при ответе JSON их по сути нету. А если посмотреть в обработчике ошибок, то видно что правильный токен получен.
Вот ссылка, что бы вам было легче ориентироваться: http://178.150.132.186/vkontakte/login
Эхх, если б я там еще был зарегистрирован ))) Тот же Facebook ведь тоже JSON отдает, и на dev.kohana-world.com все работает… Хотя, хоть об стену убейся, я не помню, где в коде идет обработка JSON’а )) В общем, попробую поковырять, может ради этого придется зарегистрироваться в МоемМире и ВК.
PS. Кстати, Вы какую ветку OAuth используете? 3.1/develop рекомендую + в моем форке есть провайдер FB (Woody почему-то до сих пор мой pull request не принял). И если буду добавлять других провайдеров (в первую очередь наших), то они сперва у меня появятся
Да, использую 3.1 develop версию, сейчас попробую FB попилить, авось получится
Если все, получится, отпишу в чем была ошибка.
Заметил ошибку в коде
// создаем объект провайдера
$this->_provider = $this->_oauth->provider($this->name);
по коду должно быть
$this->_provider = new OAuth2_Provider($this->name);
А почему ошибка-то? Класс OAuth2, каковым является переменная $this->_oauth, имеет метод provider(), он возвращает объект OAuth2_Provider.
Моя ошибка Я не в том репозитории исходник смотрел.
Какой модуля OAuth2 лучше использовать из дев https://github.com/kohana/oauth/tree/3.1/develop/classes или https://github.com/managedit/kohana-oauth2
Я пользую первый (ну или свой форк от него, если нужны свои доработки).