Контент


OAuth v2

Не так давно я написал статью «OAuth v1«, в которой рассказывал об основах работы с OAuth с помощью одноименного модуля фреймворка Kohana. Теперь модуль расширился функционально, и я, как и обещал, рассказываю о работе модуля OAuth со второй версией этой спецификации.

Опять теория…

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

Схема работы протокола OAuth v2

Как мы видим, количество шагов заметно уменьшилось. Вообще, одной из основных целей разработки новой версии стандарта стало упрощение алгоритма, избавление от особо сложных и трудоемких его частей, например от «подписывания» запросов (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';
	}
 
}

Дополнительные ссылки

Google Bookmarks Digg Reddit del.icio.us Ma.gnolia Technorati Slashdot Yahoo My Web News2.ru БобрДобр.ru RUmarkz Ваау! Memori.ru rucity.com МоёМесто.ru Mister Wong

Опубликовано в Kohana3.

Теги: , , .


Комментарии (15)

Будьте в курсе обсуждения, подпишитесь на RSS ленту комментариев к этой записи.

  1. Дмитрий пишет:

    Спасибо! Прочитал хорошую «вводную» статью в тему!

  2. stalker пишет:

    @biakaveron, не встречал модулей для Kohana, которые позволяли бы делать кроспостинг в LJ и LI блоги?

  3. biakaveron пишет:

    @stalker
    Честно говоря, не интересовался. Теоретически, если есть возможность осуществлять это через OAuth, проблем быть не должно.

  4. spiridon пишет:

    А где вообще модули для Kohana найти можно? Есть какой-то репозиторий?
    Я на гитхабе только находил сборку из кучи модулей. Типо все проекты на гитхабе как-то связанные с Kohana.

  5. biakaveron пишет:

    1. Поиск через Github по слову Kohana.
    2. Через упомянутую выше сборку модулей (по сути это что-то вроде готового результата поиска из п1).
    3. С помощью сайтов типа http://kohana-modules.com.

  6. WEST пишет:

    Спасибо! Отличная статья, помогла мне разобраться с OAuth.

    Но вот у меня проблема. Kohana выдает ошибку когда провайдер выдает ответ в формате JSON. Подскажите, пожалуйста, как это исправить?

  7. biakaveron пишет:

    Какая ошибка, какой провайдер? В общем, давайте подробности

  8. WEST пишет:

    Ошибка появляется при использовании, провайдеров МойМир и ВКонтакте.

    Ошибка появляется в функции parse_params(), Undefined offset: 1. Как я понимаю она, пытается обработать урл и получить из него GET параметры, но при ответе JSON их по сути нету. А если посмотреть в обработчике ошибок, то видно что правильный токен получен.

    Вот ссылка, что бы вам было легче ориентироваться: http://178.150.132.186/vkontakte/login

  9. biakaveron пишет:

    Эхх, если б я там еще был зарегистрирован ))) Тот же Facebook ведь тоже JSON отдает, и на dev.kohana-world.com все работает… Хотя, хоть об стену убейся, я не помню, где в коде идет обработка JSON’а )) В общем, попробую поковырять, может ради этого придется зарегистрироваться в МоемМире и ВК.

    PS. Кстати, Вы какую ветку OAuth используете? 3.1/develop рекомендую + в моем форке есть провайдер FB (Woody почему-то до сих пор мой pull request не принял). И если буду добавлять других провайдеров (в первую очередь наших), то они сперва у меня появятся ;)

  10. WEST пишет:

    Да, использую 3.1 develop версию, сейчас попробую FB попилить, авось получится :)

    Если все, получится, отпишу в чем была ошибка.

  11. fort пишет:

    Заметил ошибку в коде
    // создаем объект провайдера
    $this->_provider = $this->_oauth->provider($this->name);

    по коду должно быть
    $this->_provider = new OAuth2_Provider($this->name);

  12. biakaveron пишет:

    А почему ошибка-то? Класс OAuth2, каковым является переменная $this->_oauth, имеет метод provider(), он возвращает объект OAuth2_Provider.

  13. fort пишет:

    Моя ошибка :) Я не в том репозитории исходник смотрел.
    Какой модуля OAuth2 лучше использовать из дев https://github.com/kohana/oauth/tree/3.1/develop/classes или https://github.com/managedit/kohana-oauth2

  14. biakaveron пишет:

    Я пользую первый (ну или свой форк от него, если нужны свои доработки).

Продолжение обсуждения

  1. Kohana / Работа с OAuth v2 | crowler-pcworld ссылается на эту запись on 24 мая 2011

    [...] / Работа с OAuth v2 Предлагаю почитать статью о «прикручивании» второй версии протокола OAuth (он на [...]



Можно включить подсветку кода: <code><pre lang="">...</pre></code>
Разрешены некоторые HTML теги

или используйте trackback.