Skip to main content

OAuth2 в Rails

Для реализации OAuth2 в современном приложении на Rails 8 (для внешнего API, т.е. чтобы сторонние клиенты могли получать access tokens и обращаться к API), лучшими и наиболее актуальными решениями будут:


1. Doorkeeper — стандарт де-факто​

Плюсы:

  • Поддержка всех flows (authorization code, client credentials, password, etc.)
  • Хорошая интеграция с Rails 6–8
  • Совместим с OpenID Connect через doorkeeper-openid_connect
  • Гибкий, настраиваемый, battle-tested

Минусы:

  • Местами громоздкий, требует настройки токенов и таблиц

Инсталляция:

bundle add doorkeeper
rails generate doorkeeper:install
rails db:migrate

Документация: https://doorkeeper.gitbook.io/guides/


2. Doorkeeper + Doorkeeper::JWT — если хочешь токены в формате JWT​

Плюсы:

  • Позволяет клиентам проверять подпись токена без запроса к API
  • Удобно для stateless микросервисов

Установка:

# Gemfile
gem 'doorkeeper-jwt'

Пример конфигурации (в doorkeeper.rb):

access_token_generator '::Doorkeeper::JWT'

Doorkeeper::JWT.configure do
token_payload do |opts|
user = User.find(opts[:resource_owner_id])
{
iss: 'MyAPI',
iat: Time.current.to_i,
exp: (Time.current + 2.hours).to_i,
sub: user.id,
email: user.email
}
end

secret_key Rails.application.credentials.secret_key_base
encryption_method :hs256
end

3. Rodauth + JWT plugin (если ты не против Roda в составе API)​

Плюсы:

  • Современный, безопасный подход (полностью реализует OWASP best practices)
  • Очень гибкий, минималистичный
  • Меньше магии, чем в Devise / Doorkeeper

Минусы:

  • Не стандарт в Rails-мире, требует моста или Roda-приложения

Что НЕ использовать:​

  • devise_token_auth — устаревший подход, хрупкий, не соответствует спецификации OAuth2
  • knock — заброшен, несовместим с современным Devise и Rails
  • doorkeeper с password grant — не рекомендуется, лучше authorization_code + PKCE

Рекомендованный стек:​

  • doorkeeper + doorkeeper-jwt для токенов
  • Devise — если у тебя есть веб-интерфейс и нужна регистрация / сессии
  • Grape / Rails API-only mode — если API отдельно

Пример use-case (Client Credentials flow)​

curl -X POST http://localhost:3000/oauth/token \
-d "grant_type=client_credentials" \
-d "client_id=abc123" \
-d "client_secret=xyz456"

Что такое OAuth2​

OAuth2 — это протокол авторизации, позволяющий приложению (например, Zapier) получить ограниченный доступ к ресурсам пользователя на сервере (твоём API), без передачи пароля.


Основные роли​

РольОписание
Resource OwnerПользователь, владеющий данными
ClientВнешнее приложение (например, Zapier)
Authorization ServerВыдаёт токены (Doorkeeper)
Resource ServerAPI, который отдаёт защищённые ресурсы

Стандартный OAuth2 Flow: Authorization Code Grant​

1. Запрос на авторизацию (от клиента)​

GET /oauth/authorize
?response_type=code
&client_id=CLIENT_ID
&redirect_uri=https://client.app/callback
&scope=zapier
&state=XYZ

Пользователю показывается логин-форма. После входа:

Ответ: 302 Found с редиректом:

Location: https://client.app/callback?code=AUTH_CODE&state=XYZ

2. Обмен AUTH CODE на ACCESS TOKEN​

POST /oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=AUTH_CODE
&redirect_uri=https://client.app/callback
&client_id=CLIENT_ID
&client_secret=CLIENT_SECRET

Ответ: 200 OK

{
"access_token": "abc123",
"token_type": "Bearer",
"expires_in": 7200,
"refresh_token": "xyz789",
"scope": "zapier"
}

3. Запрос к API с Access Token​

GET /api/zapier/test
Authorization: Bearer abc123

Ответ:

{ "success": true, "account_id": 42 }

Как это реализовано в Doorkeeper​

1. Настройка doorkeeper.rb​

Doorkeeper.configure do
orm :active_record

resource_owner_authenticator do
current_user || redirect_to(login_path)
end

resource_owner_from_credentials do |routes|
user = User.find_by(email: params[:username])
user if user&.valid_password?(params[:password])
end

access_token_expires_in 2.hours
use_refresh_token
end

2. Создание клиента​

Doorkeeper::Application.create!(
name: "Zapier",
redirect_uri: "https://zapier.com/oauth/callback",
scopes: "zapier"
)

3. Протекция эндпоинтов​

class Api::Zapier::ApplicationController < Api::ApplicationController
before_action -> { doorkeeper_authorize! :zapier }
end

4. Получение пользователя из токена​

def authenticate!
return head :unauthorized unless doorkeeper_token&.accessible?

user = User.find_by(id: doorkeeper_token.resource_owner_id)
return head :unauthorized unless user

@authenticated_user = user
@account = user.account
end

Что важно помнить​

  • authorization_code grant требует UI: сначала вход, потом редирект.
  • Токен всегда жёстко привязан к resource_owner_id.
  • В API не нужно знать ничего о client_id или пароле пользователя — только access_token.
  • doorkeeper_authorize! :scope_name — проверяет, что в токене указан нужный scope.