Аутентификация OAuth2 в проектах Laravel

Время от времени возникает вопрос, как разрешить пользователям входить в отдельные (дочерние) приложения, используя одну учетную запись главного приложения.

В этой статье я расскажу, как построить такую инфраструктуру, создав центральное приложение с использованием Laravel Passport (example.com), где пользователи регистрируются один раз, а затем, используют OAuth2, дают доступ к своей учетной записи другим приложениям (app1.example.com, app2.example.com и т. д.) через Laravel Socialite. Примечание: приложению не обязательно использовать один и тот же домен.

Таким образом, пользователи смогут логиниться в дочерние приложения, не создавая новую отдельную учетную запись.

Authorization request (Laravel Passport)

Если вы еще не знакомы с протоколом OAuth2, то ниже я о нём расскажу.

Что такое OAuth2?

Прежде, чем перейти к Laravel Passport, важно понять протокол OAuth, который он реализует.

OAuth — это открытый стандарт, разработанный для обеспечения делегированного доступа к API. Например, стороннее приложения Twitter, которое может от вашего имени твитнуть в соц.сеть. Я упоминаю Twitter, поскольку разработка этого стандарта (помимо прочего) велась его ведущим разработчиком Блан Куком. Твиттер нуждался во внешней авторизации.

После первоначального релиза версии 1.0 в 2010 году протокол дозревал два года, после чего была выпущена версия 2.0. Улучшенный протокол предлагает поддержку токенов Bearer и обеспечивает «методы (называемых потоками (flows)) для авторизации веб-приложений, настольных клиентов и мобильных клиентов» (Википедия)

Давайте посмотрим, что подразумевается под терминами «токен Bearer» и «потоки авторизации».

Токены Bearer

Слово «Bearer (Предъявитель)» означает, что у вас есть определенный токен (доступ). Предъявителем является стороннее приложение, которому его выдал провайдер идентификации. Этот токен обеспечивает немедленный доступ к ресурсу, не требуя имени пользователя и пароля. С этим токеном связана вся необходимая информация, включая данные пользователя и объем возможных действий, которые может предпринять третье лицо от имени этого пользователя.

Токен Bearer обычно включается в заголовки GET или POST-запросов к конечной точке API. Конкретный пример использования показан ниже, когда отправляется GET-запрос к /api/user через библиотеку Guzzle HTTP вместе с заголовком Bearer Authorization.

$response = $client->request('GET', '/api/user', [
    'headers' => [
        'Accept' => 'application/json',
        'Authorization' => 'Bearer ' . $accessToken,
    ],
]);

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

Теперь, когда мы знаем, что такое токен доступа Bearer …как его получить?

Сначала давайте посмотрим на формальные роли в OAuth2:

  • Владелец ресурса: пользователь, который хочет войти в систему и делегировать доступ к данным своей учетной записи стороннему приложению.
  • Ресурсный сервер: сервер (API), на котором у пользователя есть учетная запись
  • Клиент: стороннее приложение, которое хочет получить доступ к информации об учетной записи на сервере ресурсов.

Протокол OAuth 2.0 выполняет стандартный обмен данными между Клиентом и Ресурсным Сервером, где каждый шаг и заданные/требуемые параметры определяются заранее. В конечном итоге Клиент получает токен доступа Bearer от Сервера. Этот процесс показан на рисунке ниже. Примечание: предполагается, что Клиент (стороннее приложение) зарегистрирован на Ресурсном Сервере.

OAuth authorization flow

После этого маленького «танца» у Клиента есть токен доступа, который может быть либо долгоживущим, либо короткоживущим (но более безопасным). Если токен доступа является короткоживущим, то Ресурсный сервер также предоставляет токен обновления (refresh token), который можно использовать, в сочетании с секретным кодом, для получения нового токена доступа способом, аналогичным шагу 3 на диаграмме выше. Примечание: Параметр «состояние» (state) используется для предотвращения CSRF атак, проверяя, не является ли запрос поддельным.

Теперь давайте посмотрим, как Laravel Passport реализует этот протокол.

Laravel Passport

В нашем примере мы хотим, чтобы провайдер идентификации (example.com) использовал Laravel Passport.

Laravel Passport — это сервер OAuth2, построенный на сервере League OAuth2. Он обеспечивает простую реализацию для существующих приложений Laravel, требуя пакет composer.

Настройка Ресурсного севера

Следуйте инструкции по установке и/или посмотрите это видео на Laracasts от Taylor Otwell. Не забудьте добавить трейт hasApiTokens в вашу модель User.

После установки у вас есть возможность добавить новых Клиентов, имеющих URL-адрес обратного вызова и автоматически сгенерированный код secret. Для каждого из «дочерних» приложений (app1.example.com, app2.example.com и т.д.) вам необходимо создать нового Клиента с собственным обратным вызовом, а пока, для примера, вы можете выбрать https://app1.example.com/login/callback (к этому мы еще вернемся).

Создание нового Клиента в Laravel Passpor
Создание нового Клиента в Laravel Passport

Laravel Passport позаботится о диалоге авторизации, предоставит код авторизации, подтвердит секретный код клиента в сочетании с кодом авторизации, и, наконец, предоставит объект User и (по умолчанию) долгоживущий токен доступа. Срок службы токенов доступа и обновления настраивается.

Пример Клиента с ID и секретным кодом
Пример Клиента с ID и секретным кодом

Laravel Socialite

Когда мы настроили Ресурсный Сервер (провайдер идентификации), нам нужно позаботиться о стороне Клиента.

Помимо Passport, Laravel предлагает пакет под названием Laravel Socialite, который позаботится о клиентской стороне при аутентификации через OAuth2.

Из коробки он позволяет аутентификацию через Facebook, Twitter, LinkedIn, Google, GitHub, GitLab и Bitbucket.

Социальные провайдеры

Однако существует ряд дополнительных провайдеров, среди которых есть адаптер, поддерживающий Laravel Passport.

Настройка Клиента

Для создания общей системы логина для нескольких приложений Laravel предлагаемое мной решение включает в себя использование провайдера Socialite для Laravel Passport.

Судя по инструкции для установки Socialite, нужно пройти несколько шагов, чтобы клиентское приложение могло идентифицировать пользователей через провайдера идентификации Laravel Passport.

  1. Установить Laravel Socialite
  2. Установить провайдера Laravel Passport для Socialite
  3. Добавить методы в LoginController
  4. Скопировать конфигурацию ‘laravelpassport‘ в config/services.php
  5. Добавить событие SocialiteWasCalled и слушатель в EventServiceProvider

Теперь, похоже, придется повторять кучу шагов для каждого клиентского приложения, которое у нас уже есть, и которое мы хотим подключиться к нашему Ресурсному Серверу. Вот почему я создал пакет (см. GitHub-репозиторий), который объединяет Laravel Socialite с драйвером Passport и может быть настроен гораздо более эффективно.

Пакет Socialite-Passport

Socialite-Passport

В приложении Клиента, которое вы хотите подключить к Ресурсному Серверу Passport, сначала установите пакет socialite-passport:

composer require jhnbrn90/socialite-passport

Опубликуйте файл конфигурации:

php artisan vendor:publish --provider="JhnBrn90\SocialitePassport\SocialitePassportServiceProvider" --tag="config"

Это перенесет файл socialite-passport.php в каталог config. В нем вы можете задать, какой контроллер (по умолчанию — LoginController, поставляемый вместе с Laravel) и какой метод следует вызывать при вызове маршрута логина (также настраивается).

return [
    'controller' => [
        'class' => \App\Http\Controllers\Auth\LoginController::class,
        'method' => 'loginWithPassport',
    ],

    'route' => [
        'name' => 'login',
        'uri' => '/login',
    ],
];

Если конфигурация дефолтная, то, после того, как Владелец ресурса предоставит Авторизацию на Ресурсном Сервере (Laravel Passport) — будет вызван метод loginWithPassport() и инжектирован объект User. Этот метод должен быть реализован в заданном контроллере (в нашем примере, LoginController):

class LoginController extends Controller 
{
    public function loginWithPassport($user) 
    {
        // пример:
        User::firstOrCreate(['name' => $user['name'], 'email' => ...]);
    }
}

Чтобы предоставить Ресурсному серверу client_id, callback_uri и т.д., вы должны добавить эти переменные в ваш файл .env:

LARAVELPASSPORT_CLIENT_ID=
LARAVELPASSPORT_CLIENT_SECRET=
LARAVELPASSPORT_REDIRECT_URI=/login/callback
LARAVELPASSPORT_HOST=https://example.com

Помните, что LARAVELPASSPORT_CLIENT_ID и LARAVELPASSPORT_CLIENT_SECRET идут с Ресурсного Сервера Laravel Passport, где сначала необходимо создать Клиента.

Пакет обеспечит соответствие маршрута, который вы задаете в переменной LARAVELPASSPORT_REDIRECT_URI и проксирует запрос через соответствующий метод и контроллер, настроенные в конфигурационном файле.

Заключение

Это все, что нужно для реализации базового функционала общей системы логина.

Надеюсь, что эта статья помогла пролить свет на протокол OAuth2 и на то, как его можно использовать с вышеупомянутыми инструментами для создания общей системы аутентификации среди различных проектов на Laravel.

Было бы неплохо также хранить токен доступа (и токен ресурса) в пользовательской модели, чтобы иметь возможность обновлять информацию всякий раз, когда пользователь изменяет свой профиль на Ресурсном Сервере. Или собирать другие данные пользователя.

Резюме

Laravel Passport реализует полнофункциональный сервер OAuth2 (Ресурсный Сервер)
Laravel Socialite реализует аутентификацию на сервере OAuth2 (Клиент)
Провайдер Socialite для Laravel Passport реализует аутентификацию через Laravel Passport
Клиенты могут быть подготовлены с помощью этого пакета, объединяющего Socialite с адаптером Passport.

Автор: John Braun
Перевод: Demiurge Ash