Rails GraphQL Auth — JWT, Email & Security
Будування безпечної та масштабованої системи автентифікації є критично важливим для сучасних веб-застосунків. Сьогодні я поділюся досвідом впровадження надійної системи автентифікації GraphQL в Rails, з детальними поясненнями кожного компоненту.
🏗️ Огляд архітектури
Система автентифікації складається з трьох основних компонентів:
- Автентифікація на основі токенів JWT
- Процес перевірки електронної пошти
- Безпечне керування паролями
Давайте розглянемо кожен компонент детальніше.
🔑 Реалізація автентифікації JWT
Генерація та керування токенами
Основою нашої автентифікації є токени JWT. Розглянемо основні компоненти:
class User < ApplicationRecord
# Генерація токенів для різних цілей з конкретними термінами дії
generates_token_for :auth_token, expires_in: 1.week
generates_token_for :email_confirmation, expires_in: 8.hours
generates_token_for :password_reset, expires_in: 1.hour
end
Цей код використовує власну систему генерації токенів, де:
generates_token_for
— це макрос, який налаштовує генерацію токенів для конкретних цілей- Кожен тип токена має свій час дії
- Токени прив'язуються до конкретних даних користувача для верифікації
Наприклад, коли ми викликаємо user.generate_token_for(:auth_token)
, це:
- Створює токен JWT із даними, що належать користувачу
- Встановлює час закінчення (1 тиждень для токенів автентифікації)
- Підписує токен за допомогою секретного ключа додатку
- Повертає закодований токен для використання клієнтом
Служба автентифікації
module Users
class SignInService < ApplicationService
def call
return failure([USER_NOT_FOUND_MESSAGE]) unless user
# Генерація токену автентифікації
token = user.generate_token_for(:auth_token)
# Логування події автентифікації
log_event(user:, data: { username: user.username })
# Повернення успішного результату з токеном та даними користувача
success({ token:, user: })
end
private
def user
# Автентифікація користувача за допомогою наданих даних
@user ||= User.authenticate_by(permitted_params)
end
end
end
Основні аспекти служби:
- Перевірка даних користувача
- Генерація токену автентифікації
- Логування події автентифікації
- Повернення структурованої відповіді
Мутація GraphQL для автентифікації
module Mutations
class UserSignIn < BaseMutationWithErrors
# Опис обов'язкових вхідних аргументів
argument :password, String, required: true
argument :username, String, required: true
# Опис полів повернення
field :token, String, null: true
field :user, Types::UserType, null: true
def resolve(**args)
result = Users::SignInService.call(args)
{
errors: result.errors,
success: result.success?,
token: result.success? ? result.data[:token] : nil,
user: result.success? ? result.data[:user] : nil
}
end
end
end
Ця мутація:
- Приймає ім’я користувача та пароль
- Викликає службу автентифікації
- Повертає токен та дані користувача в разі успіху
- Грамотно обробляє помилки
📧 Система перевірки електронної пошти
Реалізація служби підтвердження
module Users
class SendConfirmationEmailService < ApplicationService
def call
return failure([USER_NOT_FOUND_ERROR]) unless user
return failure([USER_ALREADY_CONFIRMED_ERROR]) if user.confirmed?
send_confirmation_email
log_event(user:, data: { confirmation_sent: true })
success(CONFIRMATION_SENT_MSG)
end
private
def send_confirmation_email
# Генерація листа підтвердження з безпечним токеном
email = Email.create_confirmation_email!(user:)
send_email(email)
end
end
end
Процес підтвердження:
- Перевіряє наявність користувача та його статус підтвердження
- Генерує безпечний токен підтвердження
- Створює та надсилає лист підтвердження
4.
Logs the confirmation attempt
Email Token Generation
class Email < ApplicationRecord
def self.create_confirmation_email!(user:)
token = user.generate_token_for(:email_confirmation)
create!(
to_emails: [user.email],
template_id: Rails.application.credentials.dig(:sendgrid, :confirm_template_id),
substitutions: [{
"confirmation_url": "#{Settings.emails.confirm_url}?token=#{token}",
name: user.name
}]
)
end
end
Цей код створює лист підтвердження з:
- Безпечним, обмеженим у часі токеном
- Персоналізованим URL для підтвердження
- Даними шаблону, специфічними для користувача
🔒 Реалізація безпеки
Мідлвар автентифікації
module Queries
class BaseQuery < GraphQL::Schema::Resolver
def authenticate_user!
return if current_user
raise GraphQL::ExecutionError.new(
I18n.t('gql.errors.not_authenticated'),
extensions: { code: 'AUTHENTICATION_ERROR' }
)
end
def current_user
context[:current_user] || Current.user
end
end
end
Цей мідлвар:
- Перевіряє наявність токена та його валідність
- Підтримує контекст користувача протягом всіх запитів
- Послідовно обробляє помилки автентифікації
- Надає доступ до даних поточного користувача
Безпека паролів
class User < ApplicationRecord
#A(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*(),.?":{}|<>])[A-Za-z\d!@#$%^&*(),.?":{}|<>]{8,72}\z/
validates :password,
presence: true,
length: { minimum: 8, maximum: 72 },
format: { with: PASSWORD_FORMAT },
if: :password_required?
private
def password_required?
password_digest.nil? || password.present?
end
end
Вимоги до паролю:
- Мінімум 8 символів
- Максимум 72 символи (обмеження bcrypt)
- Має містити маленькі та великі літери
- Має містити цифри та спеціальні символи
- Перевіряється тільки за необхідності
🧪 Стратегія тестування
RSpec.describe Users::SignInService do
describe '#call' do
context 'when credentials are valid' do
it 'generates an authentication token' do
result = service.call
expect(result.data[:token]).to be_present
expect(User.find_by_token_for(:auth_token, result.data[:token])).to eq(user)
end
it 'logs the authentication event' do
expect { service.call }.to change(AuditLog, :count).by(1)
end
end
context 'when credentials are invalid' do
it 'returns appropriate error messages' do
result = described_class.new(invalid_params).call
expect(result.errors).to include(I18n.t('services.users.sign_in.user_not_found'))
end
end
end
end
Наша підхід до тестування:
- Перевірка генерації та валідації токенів
- Забезпечення правильної обробки помилок
- Перевірка логування аудиту
- Перевірка обмежень безпеки
Висновок
Впровадивши ці патерни, ми створили безпечну, підтримувану систему автентифікації, яка:
- Забезпечує безпечну автентифікацію на основі токенів
- Правильно обробляє перевірку електронної пошти
- Підтримує високі стандарти безпеки
- Добре масштабується разом із розвитком застосунку
Повна реалізація демонструє, як ці компоненти працюють разом в умовах продакшн середовища, підтримуючи безпеку та користувацький досвід.
Щасливого кодування!
Оригінал опубліковано на https://sulmanweb.com.
Перекладено з: Rails GraphQL Auth — JWT, Email & Security