Introduction
APIs cannot use browser cookies for authentication. Instead, they rely on tokens — unique strings that clients include in request headers to prove their identity. Rails 8 provides built-in tools for implementing token authentication.
Key Concepts
- Token Authentication: Clients send a token in the
Authorizationheader with each request. has_secure_password: A Rails method that adds bcrypt password hashing to a model.has_secure_token: Generates a unique token for a model attribute.authenticate_by: A Rails method for secure password checking that prevents timing attacks.
Real World Context
Every API with user accounts needs authentication. Mobile apps, SPAs, and third-party integrations all send tokens with requests. The token proves the client is who they claim to be without sending credentials on every request.
Deep Dive
Setting Up User Model
ruby# Generate user model with password digest rails generate model User email:string:uniq password_digest:string api_token:string:uniq
ruby# app/models/user.rb class User < ApplicationRecord has_secure_password has_secure_token :api_token validates :email, presence: true, uniqueness: true validates :email, format: { with: URI::MailTo::EMAIL_REGEXP } end
has_secure_password adds password and password_confirmation virtual attributes and stores a bcrypt hash in password_digest. has_secure_token :api_token generates a unique 24-character token on creation.
Authentication Controller
rubyclass Api::V1::SessionsController < ApplicationController def create user = User.authenticate_by( email: params[:email], password: params[:password] ) if user render json: { token: user.api_token, user: user.as_json(only: [:id, :email]) } else render json: { error: "Invalid email or password" }, status: :unauthorized end end end
authenticate_by is a Rails method that safely looks up the user and verifies the password in constant time, preventing timing attacks that could reveal whether an email exists.
Token Verification
rubyclass ApplicationController < ActionController::API private def authenticate_user! token = request.headers["Authorization"]&.remove("Bearer ") @current_user = User.find_by(api_token: token) render json: { error: "Unauthorized" }, status: :unauthorized unless @current_user end def current_user @current_user end end
The authenticate_user! method extracts the Bearer token from the Authorization header and finds the matching user. Controllers that need authentication call this as a before_action.
Protecting Endpoints
rubyclass Api::V1::ProductsController < ApplicationController before_action :authenticate_user! before_action :set_product, only: [:show, :update, :destroy] def index render json: current_user.products end end
The before_action ensures every request to this controller is authenticated.
Common Pitfalls
- Storing tokens in plain text — While
has_secure_tokengenerates cryptographically secure tokens, consider hashing stored tokens for defense in depth. - Not using
authenticate_by— Manual email lookup +authenticateis vulnerable to timing attacks that reveal which emails exist in your system.
Best Practices
- Use
authenticate_by— It's the secure, Rails-recommended way to verify credentials. - Always use Bearer token format —
Authorization: Bearer <token>is the standard format.
Summary
- APIs use token-based authentication via the
Authorization: Bearer <token>header. has_secure_passwordhandles password hashing with bcrypt.has_secure_tokengenerates unique API tokens.authenticate_byprevents timing attacks during credential verification.- Protect endpoints with
before_action :authenticate_user!.
Code Examples
# Secure login with authenticate_by (Rails 7.1+)
user = User.authenticate_by(
email: params[:email],
password: params[:password]
)
# Returns the user if credentials match, nil otherwise.
# Uses constant-time comparison to prevent timing attacks.
# Safer than: User.find_by(email: email)&.authenticate(password)