Skip to main content

Authentication Overview

Gumroad uses multiple authentication mechanisms to support various access patterns:
  • Devise - Primary user authentication for web and API
  • Doorkeeper - OAuth 2.0 provider for third-party integrations
  • OmniAuth - Social login (Facebook, Google, Twitter, Apple)
  • Pundit - Authorization and access control policies

Devise Configuration

Devise handles user authentication with several custom configurations.

Setup

Devise is configured in config/initializers/devise.rb:
Devise.setup do |config|
  config.secret_key = ENV.fetch("DEVISE_SECRET_KEY")
  config.mailer = "UserSignupMailer"
  config.authentication_keys = [:login]
  config.case_insensitive_keys = [:login]
  config.strip_whitespace_keys = [:login]
end
The authentication key is :login which accepts either username or email, providing flexibility for users.

Login Methods

Users can authenticate using:
  1. Email/Username + Password
  2. Two-Factor Authentication (via active_model_otp)
  3. OAuth (Facebook, Google, Twitter, Apple)

Development Login

For local development, use these test credentials:
Email: [email protected]
Password: password
2FA Code: 000000
Access the application at https://gumroad.dev after starting the development server with bin/dev.

OAuth with Doorkeeper

Doorkeeper provides OAuth 2.0 server functionality, allowing third-party applications to access Gumroad APIs on behalf of users.

Configuration

Doorkeeper is configured in config/initializers/doorkeeper.rb:
Doorkeeper.configure do
  base_controller "ApplicationController"
  orm :active_record
  
  resource_owner_authenticator do
    current_user.presence || redirect_to("/oauth/login?next=#{CGI.escape request.fullpath}")
  end
  
  authorization_code_expires_in 10.minutes
  access_token_expires_in nil  # Tokens don't expire
  
  grant_flows %w[authorization_code client_credentials password]
end

OAuth Scopes

Doorkeeper defines granular scopes for API access:
  • view_public - Default scope, view public information
  • edit_products - Create and modify products
  • view_sales - View sales data
  • view_payouts - View payout information
  • mark_sales_as_shipped - Update shipping status
  • refund_sales - Process refunds
  • edit_sales - Modify sale records
  • revenue_share - Access revenue share data
  • ifttt - IFTTT integration scope
  • mobile_api - Mobile app access
  • creator_api - Creator API access
  • view_profile - View user profile
  • helper_api - Helper widget API

Grant Flows

Doorkeeper supports three OAuth 2.0 grant flows:
1

Authorization Code

Standard OAuth flow for web applications. Users authorize access, and the app receives an authorization code to exchange for an access token.
2

Client Credentials

For server-to-server authentication without a specific user context.
3

Resource Owner Password

Direct authentication with username/password. Used for mobile apps and trusted clients.

Resource Owner Password Flow

The password grant supports multiple authentication methods:
resource_owner_from_credentials do |_routes|
  if params.key?(:facebookToken)
    profile = User.fb_object("me", token: params[:facebookToken])
    user = User.find_by(facebook_uid: profile["id"])
  elsif params.key?(:twitterToken)
    user = User.where(twitter_oauth_token: params[:twitterToken]).first
  elsif params.key?(:appleAuthorizationCode)
    user = User.find_for_apple_auth(
      authorization_code: params[:appleAuthorizationCode],
      app_type: params[:appleAppType]
    )
  elsif params.key?(:googleIdToken)
    user = User.find_for_google_mobile_auth(
      google_id_token: params[:googleIdToken]
    )
  else
    user = User.where("username = ? OR email = ?", 
                      params[:username], params[:username]).first
    next unless user&.valid_password?(params[:password])
  end
  user if user&.alive?
end
This flexible approach allows mobile apps to authenticate using social providers or traditional credentials through a single endpoint.

Social Authentication (OmniAuth)

Gumroad integrates with major social providers using OmniAuth:

Supported Providers

  • Facebook - omniauth-facebook
  • Google - omniauth-google-oauth2
  • Twitter - omniauth-twitter
  • Apple - apple_id gem
  • Stripe Connect - omniauth-stripe-connect

Configuration

OmniAuth providers are configured in config/initializers/omniauth.rb and referenced in Devise initializer:
require "omniauth-facebook"
require "omniauth-twitter"
require "omniauth-google-oauth2"

CSRF Protection

Cross-site request forgery protection is enabled:
gem "omniauth-rails_csrf_protection", "~> 1.0"
Always use CSRF protection with OmniAuth to prevent authorization code interception attacks.

Authorization with Pundit

Pundit provides object-oriented authorization through policy classes.

Policy Structure

Policies are defined in app/policies/ and follow this pattern:
class ProductPolicy < ApplicationPolicy
  def update?
    user.present? && (record.user == user || user.admin?)
  end
  
  def destroy?
    user.present? && record.user == user
  end
end

Controller Integration

Policies are called in controllers:
class ProductsController < ApplicationController
  def update
    @product = Product.find(params[:id])
    authorize @product  # Calls ProductPolicy#update?
    
    if @product.update(product_params)
      redirect_to @product
    else
      render :edit
    end
  end
end

Policy Scopes

Scopes filter collections based on permissions:
class ProductPolicy < ApplicationPolicy
  class Scope < Scope
    def resolve
      if user.admin?
        scope.all
      else
        scope.where(user: user)
      end
    end
  end
end

Security Best Practices

Password Security

Devise is configured with devise-pwned_password to check passwords against known breaches.

Rate Limiting

rack-attack provides rate limiting for authentication endpoints to prevent brute force attacks.

Session Security

Sessions are stored in Redis with secure cookies. HTTPS is enforced in production via rack-ssl.

Two-Factor Auth

TOTP-based 2FA is available via active_model_otp for enhanced account security.

API Authentication

Access Token Usage

API requests use Bearer token authentication:
curl https://api.gumroad.com/v2/products \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Token Management

  • Access tokens don’t expire by default (access_token_expires_in nil)
  • Refresh tokens are enabled (use_refresh_token)
  • Authorization codes expire after 10 minutes
Each OAuth application must have an owner (enabled with enable_application_owner confirmation: true).

Testing Authentication

RSpec Helpers

Devise provides test helpers for authentication:
RSpec.describe "Dashboard", type: :system do
  let(:seller) { create(:named_seller) }
  
  before do
    login_as seller
  end
  
  it "displays dashboard stats" do
    visit dashboard_path
    expect(page).to have_text("Balance")
  end
end

Testing OAuth

OAuth flows can be tested using VCR cassettes to record and replay API responses:
VCR.use_cassette("oauth/facebook_auth") do
  # OAuth test code
end
Scope VCR cassettes to specific test files to avoid cassette collisions where tests read incorrect cached responses.

Environment Variables

Required authentication configuration:
DEVISE_SECRET_KEY=your_devise_secret
FACEBOOK_APP_ID=your_facebook_app_id
FACEBOOK_APP_SECRET=your_facebook_secret
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_secret

Next Steps

Architecture

Learn about the overall system architecture

Testing

Write tests for authentication flows

API Reference

Explore the API documentation

Contributing

Contribute to authentication features

Build docs developers (and LLMs) love