Rails GraphQL Авторизація — JWT, Електронна пошта та Безпека

pic

Rails GraphQL Auth — JWT, Email & Security

Будування безпечної та масштабованої системи автентифікації є критично важливим для сучасних веб-застосунків. Сьогодні я поділюся досвідом впровадження надійної системи автентифікації GraphQL в Rails, з детальними поясненнями кожного компоненту.

🏗️ Огляд архітектури

Система автентифікації складається з трьох основних компонентів:

  1. Автентифікація на основі токенів JWT
  2. Процес перевірки електронної пошти
  3. Безпечне керування паролями

Давайте розглянемо кожен компонент детальніше.

🔑 Реалізація автентифікації 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), це:

  1. Створює токен JWT із даними, що належать користувачу
  2. Встановлює час закінчення (1 тиждень для токенів автентифікації)
  3. Підписує токен за допомогою секретного ключа додатку
  4. Повертає закодований токен для використання клієнтом

Служба автентифікації

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

Основні аспекти служби:

  1. Перевірка даних користувача
  2. Генерація токену автентифікації
  3. Логування події автентифікації
  4. Повернення структурованої відповіді

Мутація 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

Ця мутація:

  1. Приймає ім’я користувача та пароль
  2. Викликає службу автентифікації
  3. Повертає токен та дані користувача в разі успіху
  4. Грамотно обробляє помилки

📧 Система перевірки електронної пошти

Реалізація служби підтвердження

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

Процес підтвердження:

  1. Перевіряє наявність користувача та його статус підтвердження
  2. Генерує безпечний токен підтвердження
  3. Створює та надсилає лист підтвердження
    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

Цей код створює лист підтвердження з:

  1. Безпечним, обмеженим у часі токеном
  2. Персоналізованим URL для підтвердження
  3. Даними шаблону, специфічними для користувача

🔒 Реалізація безпеки

Мідлвар автентифікації

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

Цей мідлвар:

  1. Перевіряє наявність токена та його валідність
  2. Підтримує контекст користувача протягом всіх запитів
  3. Послідовно обробляє помилки автентифікації
  4. Надає доступ до даних поточного користувача

Безпека паролів

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

Наша підхід до тестування:

  1. Перевірка генерації та валідації токенів
  2. Забезпечення правильної обробки помилок
  3. Перевірка логування аудиту
  4. Перевірка обмежень безпеки

Висновок

Впровадивши ці патерни, ми створили безпечну, підтримувану систему автентифікації, яка:

  • Забезпечує безпечну автентифікацію на основі токенів
  • Правильно обробляє перевірку електронної пошти
  • Підтримує високі стандарти безпеки
  • Добре масштабується разом із розвитком застосунку

Повна реалізація демонструє, як ці компоненти працюють разом в умовах продакшн середовища, підтримуючи безпеку та користувацький досвід.

Щасливого кодування!

Оригінал опубліковано на https://sulmanweb.com.

Перекладено з: Rails GraphQL Auth — JWT, Email & Security

Leave a Reply

Your email address will not be published. Required fields are marked *