CrackedRuby CrackedRuby

Overview

API security encompasses the strategies, protocols, and implementations that protect application programming interfaces from unauthorized access, data breaches, and malicious attacks. As APIs serve as the connective tissue between modern distributed systems, they represent critical attack surfaces that require multi-layered security controls.

The security challenge stems from APIs exposing application functionality and data to external consumers, often across network boundaries. Unlike traditional web applications where the user interface provides implicit control over interactions, APIs must enforce security programmatically at every endpoint. Each request carries potential risk - unauthorized access attempts, injection attacks, data exfiltration, or denial of service attacks.

API security operates across multiple dimensions. Authentication verifies caller identity. Authorization determines permitted actions. Encryption protects data in transit. Input validation prevents injection attacks. Rate limiting mitigates abuse. Logging and monitoring detect anomalies. Each dimension addresses specific threat vectors while contributing to defense in depth.

The shift toward microservices architectures and mobile applications has amplified API security importance. APIs now handle sensitive operations previously confined to server-side code - payment processing, personal data access, privilege escalation. A single compromised API endpoint can expose entire systems. The 2023 OWASP API Security Top 10 identifies broken object level authorization, broken authentication, and excessive data exposure as leading vulnerabilities.

Modern API security extends beyond perimeter defenses. Zero-trust architectures assume breach and verify every request. Token-based authentication eliminates shared secrets. Fine-grained authorization policies enforce least-privilege access. Encryption protects sensitive data even when systems are compromised. Security headers defend against cross-origin attacks and clickjacking.

# Basic secured API endpoint structure
class API::V1::UsersController < ApplicationController
  before_action :authenticate_request
  before_action :authorize_user, only: [:update, :destroy]
  
  def show
    @user = User.find(params[:id])
    render json: @user.safe_attributes
  rescue ActiveRecord::RecordNotFound
    render json: { error: "Not found" }, status: :not_found
  end
  
  private
  
  def authenticate_request
    token = request.headers['Authorization']&.split(' ')&.last
    @current_user = decode_token(token)
    render json: { error: "Unauthorized" }, status: :unauthorized unless @current_user
  end
  
  def authorize_user
    user = User.find(params[:id])
    render json: { error: "Forbidden" }, status: :forbidden unless can_access?(user)
  end
end

Key Principles

Authentication establishes identity. APIs must verify that callers are who they claim to be before processing requests. Authentication mechanisms range from simple API keys to complex OAuth 2.0 flows. The selected approach depends on use case - server-to-server communication differs from user-facing mobile applications. Strong authentication prevents unauthorized access and provides audit trails for security incidents.

Token-based authentication has become the dominant pattern for stateless APIs. The server issues cryptographically signed tokens after successful credential verification. Clients include tokens in subsequent requests. The server validates token signatures without maintaining session state. JSON Web Tokens (JWT) encode claims about the authenticated entity along with expiration times and issuer information. Token-based approaches scale horizontally and work across distributed systems.

Authorization determines what authenticated callers can do. Authentication answers "who are you?" Authorization answers "what can you access?" Role-based access control (RBAC) assigns permissions to roles, then assigns roles to users. Attribute-based access control (ABAC) evaluates policies against user attributes, resource properties, and environmental conditions. Authorization policies enforce principle of least privilege - granting minimum necessary access.

Object-level authorization protects individual resources. API endpoints often accept object identifiers in parameters or request bodies. Broken object level authorization occurs when the server fails to verify that the authenticated user can access the specified object. Attackers modify identifiers to access resources belonging to other users. Every endpoint must validate that the current user owns or has permission for requested resources.

Encryption protects data confidentiality. Transport Layer Security (TLS) encrypts data in transit between clients and servers. All production APIs must use HTTPS. TLS prevents eavesdropping, tampering, and man-in-the-middle attacks. Certificate validation ensures clients connect to legitimate servers. Perfect forward secrecy generates unique session keys that cannot be compromised retroactively.

Sensitive data requires encryption at rest. API servers store credentials, tokens, and user data in databases. Encryption transforms plaintext into ciphertext using cryptographic algorithms. Only systems possessing decryption keys can access the original data. Field-level encryption protects specific attributes like credit card numbers or social security numbers.

Input validation sanitizes all data received from clients. Injection attacks exploit applications that trust user input. SQL injection inserts malicious database commands. Command injection executes arbitrary system commands. Cross-site scripting (XSS) injects malicious JavaScript. APIs must validate input types, formats, and ranges. Parameterized queries prevent SQL injection. Escaping output prevents XSS. Allowlists restrict input to known-safe values.

Rate limiting prevents abuse. APIs expose computational resources that attackers can exhaust through repeated requests. Rate limiting restricts the number of requests from a single source within a time window. Implementations track request counts per API key, IP address, or user identifier. Exceeded limits trigger HTTP 429 responses. Rate limiting mitigates denial of service attacks, brute force attempts, and data scraping.

Defense in depth applies multiple security layers. No single control provides complete protection. Authentication combined with authorization prevents both unauthorized access and privilege escalation. Encryption combined with input validation protects against eavesdropping and injection attacks. Monitoring combined with rate limiting detects and stops suspicious behavior. Overlapping controls create redundancy - if one layer fails, others maintain security.

# Comprehensive security middleware stack
class SecureAPI
  def initialize(app)
    @app = app
  end
  
  def call(env)
    request = Rack::Request.new(env)
    
    # Enforce HTTPS in production
    return redirect_to_https(request) unless secure_request?(request)
    
    # Validate authentication token
    token = extract_token(request)
    user = authenticate_token(token)
    return unauthorized_response unless user
    
    # Check rate limits
    return rate_limit_exceeded if rate_limited?(user)
    
    # Add security headers
    status, headers, body = @app.call(env)
    headers.merge!(security_headers)
    
    [status, headers, body]
  end
  
  private
  
  def security_headers
    {
      'Strict-Transport-Security' => 'max-age=31536000; includeSubDomains',
      'X-Frame-Options' => 'DENY',
      'X-Content-Type-Options' => 'nosniff',
      'X-XSS-Protection' => '1; mode=block',
      'Content-Security-Policy' => "default-src 'self'"
    }
  end
end

Security Implications

Broken object level authorization represents the most critical API vulnerability. APIs accept object identifiers that specify which resources to return, modify, or delete. Without proper authorization checks, attackers substitute identifiers to access resources belonging to other users. The vulnerability stems from relying on client-provided identifiers without verifying ownership. Every endpoint that accepts resource identifiers must validate that the authenticated user has permission to access the specified resource.

Consider an API endpoint that returns user profiles: GET /api/users/:id. The naive implementation retrieves the requested user record and returns it. An attacker authenticates as user 100, then requests /api/users/200 to access another user's profile. Proper authorization compares the authenticated user identity against the requested resource owner before returning data.

Broken authentication compromises the foundation of API security. Common authentication failures include weak password policies, credential stuffing vulnerabilities, missing token expiration, and insecure token storage. APIs that accept credentials in URLs expose them in server logs and browser history. APIs that fail to invalidate tokens after password changes allow compromised tokens to remain valid indefinitely. Multi-factor authentication adds a second verification factor, significantly reducing account takeover risk.

Excessive data exposure occurs when APIs return more data than necessary. Developers often return entire database objects to clients, exposing fields like password hashes, internal identifiers, or sensitive attributes. The principle of minimal disclosure dictates that APIs return only required fields. Serialization layers should explicitly define exposed attributes rather than defaulting to all fields.

# Vulnerable - exposes all user attributes
def show
  render json: User.find(params[:id])
end

# Secure - explicitly defines exposed attributes
def show
  user = User.find(params[:id])
  render json: {
    id: user.id,
    username: user.username,
    email: user.email,
    created_at: user.created_at
  }
end

Lack of rate limiting enables denial of service and brute force attacks. APIs without request throttling allow attackers to overwhelm servers with traffic, exhaust database connections, or attempt credential guessing at scale. Rate limiting implementations track request counts per time window and reject excessive requests. Different endpoints may warrant different limits - authentication endpoints require stricter limits than read-only operations.

Mass assignment vulnerabilities occur when APIs automatically bind client-provided parameters to model attributes without filtering. An attacker includes unexpected parameters in requests to modify protected fields. The user registration endpoint accepts username and password but fails to reject an admin parameter. The attacker includes admin=true in the registration request to gain elevated privileges. Parameter filtering explicitly defines permitted attributes.

# Vulnerable to mass assignment
def create
  user = User.create(params[:user])
  render json: user
end

# Protected with strong parameters
def create
  user = User.create(user_params)
  render json: user
end

private

def user_params
  params.require(:user).permit(:username, :email, :password)
end

Security misconfiguration manifests in numerous ways. Default credentials remain unchanged. Debug endpoints stay enabled in production. Verbose error messages reveal stack traces and database schema. CORS policies allow all origins. Security headers are missing. The attack surface expands with each misconfiguration. Configuration management must explicitly set secure defaults and validate settings during deployment.

Injection attacks exploit insufficient input validation. SQL injection embeds malicious SQL in user input that gets concatenated into database queries. Command injection includes shell metacharacters in parameters that get passed to system commands. NoSQL injection manipulates queries in document databases. LDAP injection targets directory services. XML External Entity (XXE) injection exploits XML parsers to read files or cause denial of service.

Insufficient logging and monitoring blinds defenders to attacks in progress. APIs should log authentication failures, authorization violations, input validation failures, and security-relevant events. Logs must include sufficient context - timestamp, user identifier, IP address, requested resource, action attempted. Automated monitoring detects anomalous patterns like spike in failed authentication or unusual geographic access patterns. Alerting notifies security teams of potential incidents.

Insecure deserialization creates remote code execution risks. APIs that deserialize untrusted data without validation allow attackers to inject malicious objects. During deserialization, the application instantiates objects and may execute code defined in object methods. Attackers craft serialized payloads that instantiate dangerous classes or trigger side effects during deserialization. Avoid deserializing untrusted data or use safe serialization formats like JSON.

Cross-Origin Resource Sharing (CORS) misconfigurations expose APIs to cross-origin attacks. Browsers prevent JavaScript from making requests to different origins unless the server explicitly allows it via CORS headers. Setting Access-Control-Allow-Origin: * permits all origins, enabling malicious sites to make authenticated requests from victim browsers. CORS policies should explicitly list allowed origins and validate the Origin header.

Ruby Implementation

Rails provides built-in security features for API development. The ActionController::API base class offers a lightweight controller stack optimized for API responses. Rails automatically protects against CSRF attacks, SQL injection through ActiveRecord, and mass assignment through strong parameters. The security foundation requires explicit opt-in for additional protections.

Authentication in Rails APIs typically uses token-based approaches. The has_secure_password method provides bcrypt password hashing. Developers implement custom authentication controllers that verify credentials and issue tokens. The JWT gem generates JSON Web Tokens containing encrypted claims. Controllers include authentication checks in before_action callbacks.

# Authentication controller
class API::AuthenticationController < ApplicationController
  skip_before_action :authenticate_request
  
  def create
    user = User.find_by(email: params[:email])
    
    if user&.authenticate(params[:password])
      token = generate_token(user)
      render json: { token: token, user: user.as_json(only: [:id, :email]) }
    else
      render json: { error: "Invalid credentials" }, status: :unauthorized
    end
  end
  
  private
  
  def generate_token(user)
    payload = {
      user_id: user.id,
      exp: 24.hours.from_now.to_i
    }
    JWT.encode(payload, Rails.application.credentials.secret_key_base)
  end
end

# Application controller with authentication
class ApplicationController < ActionController::API
  before_action :authenticate_request
  
  private
  
  def authenticate_request
    header = request.headers['Authorization']
    token = header.split(' ').last if header
    
    begin
      decoded = JWT.decode(token, Rails.application.credentials.secret_key_base).first
      @current_user = User.find(decoded['user_id'])
    rescue JWT::ExpiredSignature
      render json: { error: "Token expired" }, status: :unauthorized
    rescue JWT::DecodeError, ActiveRecord::RecordNotFound
      render json: { error: "Invalid token" }, status: :unauthorized
    end
  end
  
  attr_reader :current_user
end

The Pundit gem provides authorization policies. Each model has a corresponding policy class defining who can perform which actions. Controllers call authorize methods before operations. Policies encapsulate authorization logic in dedicated classes rather than scattering conditional checks across controllers.

# User policy
class UserPolicy < ApplicationPolicy
  def show?
    user == record || user.admin?
  end
  
  def update?
    user == record
  end
  
  def destroy?
    user.admin?
  end
end

# Controller using Pundit
class API::UsersController < ApplicationController
  def show
    @user = User.find(params[:id])
    authorize @user
    render json: @user
  end
  
  def update
    @user = User.find(params[:id])
    authorize @user
    
    if @user.update(user_params)
      render json: @user
    else
      render json: { errors: @user.errors }, status: :unprocessable_entity
    end
  end
end

Rate limiting protection uses Rack::Attack middleware. The gem provides a DSL for defining throttle rules, blocklists, and allowlists. Throttles limit requests per time period. Blocklists reject traffic from specific IPs or patterns. Allowlists exempt trusted sources from restrictions.

# config/initializers/rack_attack.rb
class Rack::Attack
  # Throttle authentication attempts
  throttle('auth/ip', limit: 5, period: 60) do |req|
    req.ip if req.path == '/api/auth' && req.post?
  end
  
  # Throttle API requests per authenticated user
  throttle('api/user', limit: 100, period: 60) do |req|
    req.env['current_user']&.id if req.path.start_with?('/api')
  end
  
  # Block IPs making excessive requests
  blocklist('block bad actors') do |req|
    Blocklist.blocked?(req.ip)
  end
  
  # Custom response for throttled requests
  self.throttled_response = lambda do |env|
    retry_after = env['rack.attack.match_data'][:period]
    [
      429,
      {
        'Content-Type' => 'application/json',
        'Retry-After' => retry_after.to_s
      },
      [{ error: 'Rate limit exceeded', retry_after: retry_after }.to_json]
    ]
  end
end

OAuth 2.0 implementation uses the Doorkeeper gem. Doorkeeper provides authorization server functionality, generating access tokens and refresh tokens. The gem integrates with existing authentication systems and supports multiple grant types - authorization code, client credentials, resource owner password credentials.

# Doorkeeper configuration
Doorkeeper.configure do
  orm :active_record
  
  resource_owner_authenticator do
    User.find_by(id: session[:user_id]) || redirect_to(login_url)
  end
  
  # Access token expiration
  access_token_expires_in 2.hours
  
  # Grant flows to enable
  grant_flows %w[authorization_code client_credentials]
  
  # Token authentication methods
  access_token_methods :from_bearer_authorization
end

# Protected API endpoint
class API::ResourcesController < ApplicationController
  before_action -> { doorkeeper_authorize! :read }
  
  def index
    @resources = Resource.where(user: current_resource_owner)
    render json: @resources
  end
  
  private
  
  def current_resource_owner
    User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token
  end
end

Secure password storage requires proper hashing. The bcrypt gem implements the bcrypt algorithm, designed specifically for password hashing. Bcrypt includes automatic salt generation and configurable work factors. Never store passwords in plaintext or use weak hashing algorithms like MD5 or SHA1.

class User < ApplicationRecord
  has_secure_password
  
  validates :email, presence: true, uniqueness: true
  validates :password, length: { minimum: 12 }, if: :password_required?
  
  private
  
  def password_required?
    password_digest.blank? || password.present?
  end
end

API versioning maintains backward compatibility while introducing security improvements. URL-based versioning embeds version numbers in paths. Header-based versioning uses custom headers or Accept headers. Deprecated versions can enforce stricter security requirements or reject requests entirely.

# Version-based routing
Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do
      resources :users
    end
    
    namespace :v2 do
      resources :users
    end
  end
end

# Version-specific security requirements
module API
  module V1
    class ApplicationController < ActionController::API
      before_action :deprecation_warning
      
      private
      
      def deprecation_warning
        response.headers['Warning'] = '299 - "API v1 is deprecated. Migrate to v2"'
      end
    end
  end
end

Implementation Approaches

API key authentication provides simple authentication for server-to-server communication. The API provider generates unique keys for each client. Clients include keys in request headers or query parameters. The server validates keys against stored values. API keys work well for identifying applications but cannot represent individual users. Key rotation policies mitigate compromise risk. Never embed API keys in client-side code or public repositories.

# API key generation and storage
class APIKey < ApplicationRecord
  belongs_to :user
  
  before_create :generate_key
  
  private
  
  def generate_key
    self.key = SecureRandom.hex(32)
    self.key_hash = Digest::SHA256.hexdigest(key)
  end
  
  def self.authenticate(key)
    key_hash = Digest::SHA256.hexdigest(key)
    find_by(key_hash: key_hash)
  end
end

# Controller authentication
def authenticate_api_key
  key = request.headers['X-API-Key']
  @api_key = APIKey.authenticate(key)
  render json: { error: "Invalid API key" }, status: :unauthorized unless @api_key
end

OAuth 2.0 handles delegated authorization. Users grant third-party applications limited access to resources without sharing passwords. The authorization server issues access tokens after user consent. Resource servers validate tokens before serving requests. OAuth 2.0 defines multiple flows for different scenarios - authorization code for web applications, implicit for single-page apps, client credentials for service accounts.

The authorization code flow provides the strongest security. The client redirects users to the authorization server. After authentication and consent, the authorization server redirects back with an authorization code. The client exchanges the code for an access token through a direct server-to-server request. This prevents token exposure in URLs or browser history.

JWT tokens encode claims in a JSON structure. The token contains three parts: header, payload, and signature. The header specifies the signing algorithm. The payload contains claims like user ID and expiration time. The signature ensures integrity - tampering invalidates the token. JWTs enable stateless authentication since servers validate tokens cryptographically without database lookups.

# JWT encoding with custom claims
def encode_token(user, permissions = [])
  payload = {
    user_id: user.id,
    email: user.email,
    permissions: permissions,
    iat: Time.now.to_i,
    exp: 24.hours.from_now.to_i
  }
  
  JWT.encode(payload, jwt_secret, 'HS256')
end

# JWT decoding with verification
def decode_token(token)
  JWT.decode(
    token,
    jwt_secret,
    true,
    {
      algorithm: 'HS256',
      verify_expiration: true,
      verify_iat: true
    }
  ).first
rescue JWT::DecodeError => e
  nil
end

Session-based authentication maintains state on the server. After successful login, the server creates a session and returns a session identifier to the client. Subsequent requests include the session ID. The server looks up session data to authenticate requests. Sessions work well for traditional web applications but scale poorly for distributed systems. Session stores can use databases, Redis, or memory stores.

Mutual TLS authentication verifies both client and server identities using certificates. The server requires clients to present valid certificates signed by a trusted authority. Both parties verify certificates during the TLS handshake. Mutual TLS provides strong authentication for service-to-service communication. Certificate management requires infrastructure for issuance, renewal, and revocation.

Token refresh mechanisms extend authentication without requiring reauthentication. Short-lived access tokens minimize risk if compromised. Long-lived refresh tokens enable obtaining new access tokens. When an access token expires, the client presents the refresh token to receive a new access token. The authorization server can revoke refresh tokens, terminating access immediately.

# Token refresh implementation
class API::TokensController < ApplicationController
  skip_before_action :authenticate_request, only: [:refresh]
  
  def refresh
    refresh_token = params[:refresh_token]
    stored_token = RefreshToken.find_by(token_hash: hash_token(refresh_token))
    
    if stored_token&.valid?
      new_access_token = generate_access_token(stored_token.user)
      render json: { access_token: new_access_token }
    else
      render json: { error: "Invalid refresh token" }, status: :unauthorized
    end
  end
  
  private
  
  def hash_token(token)
    Digest::SHA256.hexdigest(token)
  end
end

Common Pitfalls

Hardcoded credentials in source code expose secrets to anyone with repository access. Developers often commit API keys, database passwords, or signing secrets directly in code. Version control systems preserve this information in commit history even after removal. Use environment variables or secret management services for sensitive values. Scan repositories for accidentally committed secrets.

Weak token generation creates predictable values attackers can guess. Sequential integers, timestamps, or simple random numbers fail to provide sufficient entropy. Use cryptographically secure random number generators. Ruby's SecureRandom module generates tokens with adequate randomness. Tokens should have sufficient length - 32 bytes minimum for API keys.

# Weak token generation - predictable
token = rand(1000000).to_s  # Bad

# Secure token generation
token = SecureRandom.hex(32)  # Good
token = SecureRandom.urlsafe_base64(32)  # Also good

Missing token expiration allows compromised tokens to remain valid indefinitely. Tokens should include expiration times and servers must validate them. Short expiration windows limit damage from token compromise. Refresh tokens enable extending sessions without keeping access tokens valid for extended periods.

Exposing detailed error messages reveals system internals to attackers. Stack traces expose code structure, file paths, and dependencies. Database errors reveal schema information. Verbose messages help attackers map the attack surface. Return generic error messages to clients while logging detailed information server-side.

# Detailed error exposure - vulnerable
rescue ActiveRecord::RecordNotFound => e
  render json: { error: e.message, backtrace: e.backtrace }, status: :not_found
end

# Generic error messages - secure  
rescue ActiveRecord::RecordNotFound
  render json: { error: "Resource not found" }, status: :not_found
  # Log detailed error server-side
  Rails.logger.error("Record not found: #{e.message}")
end

Insufficient rate limiting enables brute force attacks and API abuse. Authentication endpoints without rate limits allow unlimited password guessing attempts. Resource-intensive operations without limits exhaust server capacity. Apply different rate limits based on endpoint sensitivity and resource consumption.

Trusting client-provided data without validation creates injection vulnerabilities. Assume all input is malicious until proven otherwise. Validate data types, formats, and ranges. Use parameterized queries for database operations. Escape output when rendering user-provided content. Apply allowlists for known-safe values rather than blocklists for dangerous patterns.

Using GET requests for state-changing operations violates HTTP semantics and creates security issues. GET requests appear in server logs, browser history, and may be cached by intermediaries. Credentials or sensitive data in URLs gets exposed in multiple places. Use POST, PUT, PATCH, or DELETE for operations that modify state.

Failing to validate Content-Type headers enables type confusion attacks. Attackers send JSON payloads with Content-Type: text/html to bypass JSON parsing protections. Validate that Content-Type matches expected values before processing request bodies. Reject requests with mismatched content types.

Missing security headers leave applications vulnerable to browser-based attacks. Strict-Transport-Security enforces HTTPS. X-Frame-Options prevents clickjacking. Content-Security-Policy restricts resource loading. X-Content-Type-Options prevents MIME sniffing. Set security headers on all API responses.

# Comprehensive security headers
class API::ApplicationController < ActionController::API
  after_action :set_security_headers
  
  private
  
  def set_security_headers
    response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains; preload'
    response.headers['X-Frame-Options'] = 'DENY'
    response.headers['X-Content-Type-Options'] = 'nosniff'
    response.headers['X-XSS-Protection'] = '1; mode=block'
    response.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin'
    response.headers['Permissions-Policy'] = 'geolocation=(), microphone=(), camera=()'
  end
end

Inadequate CORS configuration creates cross-origin security issues. Setting Access-Control-Allow-Origin: * permits all origins including malicious sites. Validate Origin headers and explicitly list allowed origins. Never reflect the Origin header in Access-Control-Allow-Origin without validation.

Logging sensitive data creates compliance and security issues. Passwords, tokens, credit card numbers, and personally identifiable information should never appear in logs. Filter sensitive parameters before logging. Encrypt logs at rest. Implement log retention policies.

Testing Approaches

Security testing requires different strategies than functional testing. Unit tests verify authorization logic in isolation. Integration tests confirm security controls work across components. Penetration testing simulates real attacks. Automated scanning identifies common vulnerabilities.

Authorization testing ensures policies enforce access controls correctly. Test that authenticated users can only access permitted resources. Verify that unauthorized access attempts fail appropriately. Test boundary conditions - resources the user owns versus resources belonging to others.

# RSpec authorization tests
RSpec.describe API::UsersController, type: :request do
  let(:user) { create(:user) }
  let(:other_user) { create(:user) }
  let(:admin) { create(:user, :admin) }
  
  describe 'GET /api/users/:id' do
    context 'when accessing own profile' do
      it 'returns the user profile' do
        get "/api/users/#{user.id}", headers: auth_headers(user)
        expect(response).to have_http_status(:ok)
      end
    end
    
    context 'when accessing other user profile' do
      it 'returns forbidden' do
        get "/api/users/#{other_user.id}", headers: auth_headers(user)
        expect(response).to have_http_status(:forbidden)
      end
    end
    
    context 'when admin accessing any profile' do
      it 'returns the user profile' do
        get "/api/users/#{user.id}", headers: auth_headers(admin)
        expect(response).to have_http_status(:ok)
      end
    end
  end
end

Authentication testing validates credential verification and token handling. Test successful authentication with valid credentials. Test authentication failures with invalid credentials. Verify token expiration handling. Test token refresh mechanisms. Confirm that token revocation prevents access.

# Authentication test suite
RSpec.describe 'API Authentication', type: :request do
  let(:user) { create(:user, password: 'SecurePassword123!') }
  
  describe 'POST /api/auth' do
    context 'with valid credentials' do
      it 'returns a token' do
        post '/api/auth', params: {
          email: user.email,
          password: 'SecurePassword123!'
        }
        
        expect(response).to have_http_status(:ok)
        expect(json_response['token']).to be_present
      end
    end
    
    context 'with invalid password' do
      it 'returns unauthorized' do
        post '/api/auth', params: {
          email: user.email,
          password: 'wrong'
        }
        
        expect(response).to have_http_status(:unauthorized)
        expect(json_response['token']).to be_nil
      end
    end
    
    context 'with expired token' do
      it 'rejects the request' do
        token = generate_token(user, exp: 1.hour.ago)
        get '/api/protected', headers: { 'Authorization' => "Bearer #{token}" }
        
        expect(response).to have_http_status(:unauthorized)
      end
    end
  end
end

Input validation testing confirms that APIs reject malicious input. Test SQL injection attempts in query parameters. Test command injection in file operations. Test XSS payloads in text fields. Test oversized inputs that could cause denial of service. Test unexpected data types.

Rate limiting testing verifies throttling mechanisms function correctly. Make sequential requests up to the limit and confirm they succeed. Make one additional request and verify it receives a 429 status. Wait for the time window to reset and confirm requests succeed again. Test rate limiting per user, per IP, and per API key.

# Rate limiting test
RSpec.describe 'Rate Limiting', type: :request do
  let(:user) { create(:user) }
  
  it 'limits requests per time window' do
    headers = auth_headers(user)
    
    # Make requests up to limit
    5.times do
      get '/api/resources', headers: headers
      expect(response).to have_http_status(:ok)
    end
    
    # Sixth request should be rate limited
    get '/api/resources', headers: headers
    expect(response).to have_http_status(:too_many_requests)
    expect(response.headers['Retry-After']).to be_present
  end
end

Security scanning identifies vulnerabilities through automated analysis. Tools like Brakeman scan Rails applications for security issues. OWASP ZAP performs dynamic analysis by making HTTP requests. Dependency scanners identify vulnerable gems. Integrate security scanning into continuous integration pipelines.

Penetration testing involves manual security assessment by skilled testers. Testers attempt to exploit vulnerabilities through creative attack techniques. Penetration testing discovers logic flaws and complex vulnerabilities that automated tools miss. Conduct penetration testing before major releases and periodically for production systems.

Fuzzing tests application behavior with random or malformed input. Fuzzers generate thousands of input variations to trigger unexpected behavior. Effective for discovering input validation gaps, crashes, and edge cases. Fuzzing tools can target API endpoints, file parsers, and protocol handlers.

Reference

Authentication Methods Comparison

Method Use Case Security Level Complexity Stateless
API Keys Server-to-server Medium Low Yes
OAuth 2.0 Third-party delegation High High Yes
JWT Distributed systems High Medium Yes
Session-based Traditional web apps Medium Low No
Mutual TLS Service mesh Very High High Yes

HTTP Security Headers

Header Purpose Example Value
Strict-Transport-Security Enforce HTTPS max-age=31536000; includeSubDomains
X-Frame-Options Prevent clickjacking DENY
X-Content-Type-Options Prevent MIME sniffing nosniff
Content-Security-Policy Restrict resource loading default-src 'self'
X-XSS-Protection Enable XSS filter 1; mode=block
Referrer-Policy Control referrer information strict-origin-when-cross-origin

Common HTTP Status Codes for Security

Code Name When to Use
401 Unauthorized Missing or invalid authentication
403 Forbidden Authenticated but lacks permission
429 Too Many Requests Rate limit exceeded
400 Bad Request Invalid input format
422 Unprocessable Entity Valid format but fails validation

OWASP API Security Top 10

Rank Vulnerability Description
API1 Broken Object Level Authorization Accessing objects without ownership verification
API2 Broken Authentication Weak credential handling or session management
API3 Broken Object Property Level Authorization Exposing or allowing modification of sensitive properties
API4 Unrestricted Resource Consumption Missing rate limiting or resource quotas
API5 Broken Function Level Authorization Accessing administrative functions without proper authorization
API6 Unrestricted Access to Sensitive Business Flows Missing protection for critical business operations
API7 Server Side Request Forgery API making requests to attacker-controlled URLs
API8 Security Misconfiguration Insecure defaults or missing security hardening
API9 Improper Inventory Management Undocumented or deprecated endpoints
API10 Unsafe Consumption of APIs Trusting data from third-party APIs without validation

Token Generation Best Practices

Aspect Recommendation Example
Length Minimum 32 bytes SecureRandom.hex(32)
Encoding URL-safe base64 SecureRandom.urlsafe_base64(32)
Expiration 1-24 hours for access tokens exp: 1.hour.from_now
Storage Hashed in database SHA256 hash of token
Transmission HTTPS only Never in URLs

Input Validation Checklist

Validation Type Check Implementation
Type validation Correct data type Strong parameters
Format validation Matches expected pattern Regex or format validators
Range validation Within acceptable bounds Numericality validators
Length validation Appropriate size Length validators
Allowlist validation Known safe values Inclusion validators
Sanitization Remove dangerous characters HTML escaping, SQL parameterization

Rate Limiting Strategies

Strategy Tracks By Use Case
Per IP Client IP address Anonymous endpoints
Per User Authenticated user ID User-specific actions
Per API Key API key identifier Partner integrations
Per Endpoint Request path Protecting expensive operations
Sliding Window Rolling time period Smooth traffic distribution
Token Bucket Available tokens Burst allowance

Security Testing Types

Test Type Focus Frequency
Unit tests Authorization logic Every commit
Integration tests End-to-end security flows Every build
Static analysis Code vulnerabilities Every commit
Dependency scanning Vulnerable libraries Daily
Dynamic scanning Runtime vulnerabilities Weekly
Penetration testing Manual exploitation Quarterly

Essential Ruby Security Gems

Gem Purpose Configuration
bcrypt Password hashing has_secure_password
jwt JSON Web Tokens JWT.encode/decode
doorkeeper OAuth 2.0 provider Doorkeeper.configure
pundit Authorization policies Policy classes
rack-attack Rate limiting Throttle definitions
brakeman Security scanning Static analysis
devise Authentication Full auth system
secure_headers Security header management Header configuration