Logo

dev-resources.site

for different kinds of informations.

Rails GraphQL Auth - JWT, Email & Security

Published at
1/6/2025
Categories
rails
graphql
ruby
security
Author
sulmanweb
Categories
4 categories in total
rails
open
graphql
open
ruby
open
security
open
Author
9 person written this
sulmanweb
open
Rails GraphQL Auth - JWT, Email & Security

Building a secure and scalable authentication system is crucial for modern web applications. Today, I'll share insights from implementing a robust GraphQL authentication system in Rails, with detailed explanations of each component.

🏗️ Architecture Overview

The authentication system consists of three main components:

  1. JWT-based token authentication
  2. Email verification workflow
  3. Secure password management

Let's examine each component in detail.

🔑 JWT Authentication Implementation

Token Generation and Management

The core of our authentication relies on JWT tokens. Let's break down the key components:

class User < ApplicationRecord
  # Token generation for different purposes with specific lifetimes
  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
Enter fullscreen mode Exit fullscreen mode

This code uses a custom token generation system where:

  • generates_token_for is a macro that sets up token generation for specific purposes
  • Each token type has its own expiration time
  • Tokens are bound to specific user data for verification

For example, when we call user.generate_token_for(:auth_token), it:

  1. Creates a JWT token with user-specific claims
  2. Sets an expiration time (1 week for auth tokens)
  3. Signs the token with the application's secret key
  4. Returns the encoded token for client use

Authentication Service

module Users
  class SignInService < ApplicationService
    def call
      return failure([USER_NOT_FOUND_MESSAGE]) unless user

      # Generate authentication token
      token = user.generate_token_for(:auth_token)

      # Log the authentication event
      log_event(user:, data: { username: user.username })

      # Return success response with token and user data
      success({ token:, user: })
    end

    private

    def user
      # Authenticate user using credentials
      @user ||= User.authenticate_by(permitted_params)
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Key aspects of the service:

  1. Validates user credentials
  2. Generates an authentication token
  3. Logs the authentication event
  4. Returns a structured response

GraphQL Authentication Mutation

module Mutations
  class UserSignIn < BaseMutationWithErrors
    # Define required input arguments
    argument :password, String, required: true
    argument :username, String, required: true

    # Define return fields
    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
Enter fullscreen mode Exit fullscreen mode

This mutation:

  1. Accepts username and password
  2. Calls the authentication service
  3. Returns token and user data on success
  4. Handles errors gracefully

📧 Email Verification System

Confirmation Service Implementation

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
      # Generate confirmation email with secure token
      email = Email.create_confirmation_email!(user:)
      send_email(email)
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

The confirmation flow:

  1. Checks user existence and confirmation status
  2. Generates a secure confirmation token
  3. Creates and sends confirmation email
  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
Enter fullscreen mode Exit fullscreen mode

This creates a confirmation email with:

  1. A secure, time-limited token
  2. A personalized confirmation URL
  3. User-specific template data

đź”’ Security Implementation

Authentication Middleware

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
Enter fullscreen mode Exit fullscreen mode

This middleware:

  1. Verifies token presence and validity
  2. Maintains user context throughout requests
  3. Handles authentication errors consistently
  4. Provides access to current user data

Password Security

class User < ApplicationRecord
  # Regular expression for password validation
  PASSWORD_FORMAT = /\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
Enter fullscreen mode Exit fullscreen mode

Password requirements:

  • Minimum 8 characters
  • Maximum 72 characters (bcrypt limitation)
  • Must include lowercase and uppercase letters
  • Must include numbers and special characters
  • Validated only when necessary

🧪 Testing Strategy

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
Enter fullscreen mode Exit fullscreen mode

Our testing approach:

  1. Verifies token generation and validation
  2. Ensures proper error handling
  3. Checks audit logging
  4. Validates security constraints

Conclusion

By implementing these patterns, we've created a secure, maintainable authentication system that:

  • Provides secure token-based authentication
  • Handles email verification properly
  • Maintains high security standards
  • Scales well with application growth

The complete implementation demonstrates how these components work together in a production environment while maintaining security and user experience.


Happy Coding!


Originally published at https://sulmanweb.com.

graphql Article's
30 articles in total
Favicon
GraphQL Transforming API Development
Favicon
A Beginner’s Guide to Building GraphQL APIs with Apollo Server
Favicon
AWS Cognito + GraphQL Directive = ACL with minimal effort
Favicon
How to Add GitHub Contribution Stats to Your React App
Favicon
Netlify + FalkorDB: GRAPH Database Integration for Netlify Just Got Easier
Favicon
Setup GraphQL Mock Server
Favicon
Deploy graphql project
Favicon
Applications and Advantages of GraphQL in Modern Web Applications
Favicon
Automatically Generate REST and GraphQL APIs From Your Database
Favicon
Which One Should You Choose NEST JS or EXPRESS JS?
Favicon
Simplify Content Management with spurtCMS Powerful and Flexible
Favicon
Building REST APIs vs GraphQL: Which One is Right for Your Project?
Favicon
API Design Best Practices in 2025: REST, GraphQL, and gRPC
Favicon
GraphQL vs REST: When to Choose Which for Your Node.js Backend
Favicon
Understanding the Differences Between GraphQL and REST API Gateways
Favicon
Rails GraphQL Auth - JWT, Email & Security
Favicon
Essential APIs Every Developer Should Know: A Quick Guide
Favicon
From REST to GraphQL: Why and How I Made the Switch
Favicon
An Introduction to GraphQL: Only the Data You Need
Favicon
Effortless API Scaling: Unlock the Power of AWS AppSync
Favicon
Handling Errors in GraphQL APIs💡✨
Favicon
GraphQL query complexity + NestJS + Dataloader
Favicon
My Experience with AsyncThunk in Redux Toolkit
Favicon
GraphFusion is Now Open Source – Join Us in Building the Future of AI Knowledge Graphs 🚀
Favicon
Integrating Contentful in React: A Beginner’s Guide to Content Modeling and Fetching Data with GraphQL
Favicon
Extending your GraphQL service: Federation or Schema Stitching
Favicon
15 Best GraphQL Tools for 2025
Favicon
Introduction to GraphQL
Favicon
How to attach extra data to a GraphQL response on Apollo Server
Favicon
Using WordPress as a Data Entry Site to Power a Central API

Featured ones: