Spree Commerce is an open-source Ruby on Rails e-commerce framework. It manages two user types: admin users who access the Spree admin panel, and customer accounts who shop on the storefront. User management is handled through the admin interface, Rails console, or the Spree REST API (Storefront and Platform APIs).
How Spree User Management Works
Spree's user system is powered by the spree_auth_devise gem (built on Devise) or a custom authentication adapter. The key models are:
- Spree::User -- Base user model for both admin and customer accounts
- Spree::Role -- Named roles (default:
admin). Custom roles can be added. - Spree::RoleUser -- Join table connecting users to roles
Admin access is determined by the admin role. Users without this role are treated as storefront customers.
Adding Admin Users via Admin Panel
- Log in to the Spree admin at
https://your-store.com/admin - Navigate to Settings > Users (or Users in the sidebar)
- Click New User
- Fill in the required fields:
- Email (used for login)
- Password and Password Confirmation
- Under Roles, check Admin to grant backend access
- Click Create
Adding Users via Rails Console
# Open Rails console
# bundle exec rails console
# Create an admin user
admin = Spree::User.create!(
email: 'jane@company.com',
password: 'SecurePass123!',
password_confirmation: 'SecurePass123!'
)
admin.spree_roles << Spree::Role.find_or_create_by(name: 'admin')
puts "Created admin: #{admin.email} (ID: #{admin.id})"
# Create a customer (no admin role)
customer = Spree::User.create!(
email: 'customer@example.com',
password: 'CustPass123!',
password_confirmation: 'CustPass123!'
)
puts "Created customer: #{customer.email} (ID: #{customer.id})"
Adding Users via Spree Platform API
The Platform API (v2) provides admin-level endpoints for user management:
# Create a new user via Platform API
curl -X POST "https://your-store.com/api/v2/platform/users" \
-H "Authorization: Bearer $PLATFORM_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"user": {
"email": "jane@company.com",
"password": "SecurePass123!",
"password_confirmation": "SecurePass123!",
"spree_role_ids": []
}
}'
# Create an admin user (include admin role ID)
# First, get the admin role ID
curl "https://your-store.com/api/v2/platform/roles" \
-H "Authorization: Bearer $PLATFORM_API_TOKEN"
# Then create with admin role
curl -X POST "https://your-store.com/api/v2/platform/users" \
-H "Authorization: Bearer $PLATFORM_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"user": {
"email": "admin@company.com",
"password": "AdminPass123!",
"password_confirmation": "AdminPass123!",
"spree_role_ids": [1]
}
}'
Storefront API Account Creation
Customers self-register via the Storefront API:
# Customer self-registration via Storefront API
curl -X POST "https://your-store.com/api/v2/storefront/account" \
-H "Content-Type: application/json" \
-d '{
"user": {
"email": "shopper@example.com",
"password": "ShopperPass123!",
"password_confirmation": "ShopperPass123!"
}
}'
Bulk User Management
Rake Task for Bulk Import
# lib/tasks/import_users.rake
namespace :spree do
namespace :users do
desc 'Bulk import users from CSV'
task import: :environment do
require 'csv'
file = ENV['CSV_FILE'] || 'tmp/users.csv'
admin_role = Spree::Role.find_or_create_by(name: 'admin')
created = 0
skipped = 0
CSV.foreach(file, headers: true) do |row|
if Spree::User.exists?(email: row['email'])
puts "Skipped (exists): #{row['email']}"
skipped += 1
next
end
user = Spree::User.create!(
email: row['email'],
password: row['password'] || 'TempPass123!',
password_confirmation: row['password'] || 'TempPass123!'
)
if row['role'] == 'admin'
user.spree_roles << admin_role
end
puts "Created: #{row['email']} (role: #{row['role'] || 'customer'})"
created += 1
end
puts "\nDone. Created: #{created}, Skipped: #{skipped}"
end
end
end
# Run with: bundle exec rake spree:users:import CSV_FILE=tmp/users.csv
CSV format:
email,password,role
editor1@company.com,TempPass123!,admin
editor2@company.com,TempPass456!,admin
customer1@example.com,CustPass1!,
customer2@example.com,CustPass2!,
Bulk Role Assignment
# Promote all users from a specific domain to admin
domain = 'company.com'
admin_role = Spree::Role.find_or_create_by(name: 'admin')
Spree::User.where('email LIKE ?', "%@#{domain}").find_each do |user|
unless user.has_spree_role?('admin')
user.spree_roles << admin_role
puts "Promoted: #{user.email}"
end
end
Removing and Deactivating Users
Deactivation via Devise Lock
Spree uses Devise, which supports account locking:
# Lock a user account (prevents login)
user = Spree::User.find_by(email: 'jane@company.com')
user.lock_access!
puts "Locked: #{user.email}"
# Unlock later if needed
user.unlock_access!
Via admin panel:
If you have the devise :lockable strategy enabled, locked users appear with a "Locked" status in the admin Users list.
Soft Delete (If Using paranoia/discard Gem)
Many Spree installations add soft-delete support:
# With acts_as_paranoid or discard gem
user = Spree::User.find_by(email: 'jane@company.com')
user.discard # or user.destroy if using paranoia (soft deletes)
puts "Soft-deleted: #{user.email}"
# Restore later
user.undiscard # or Spree::User.with_deleted.find_by(email: 'jane@company.com').recover
Permanent Deletion
Via Admin Panel:
- Navigate to Settings > Users
- Find the user
- Click Edit
- Click Delete (or the trash icon)
- Confirm
Via Rails Console:
user = Spree::User.find_by(email: 'jane@company.com')
if user
# Reassign orders to prevent orphaned records
user.orders.update_all(user_id: nil)
user.destroy!
puts "Permanently deleted: jane@company.com"
end
What happens to their content:
- Orders retain the user's email and shipping/billing addresses but the
user_idforeign key becomes null (if user is deleted) or the association breaks - Reviews/Ratings (if using
spree_reviews) are deleted with the user unless configured otherwise - Addresses in
spree_addressesare deleted via dependent destroy - Payment sources (saved credit cards) are removed
- Wishlist items are deleted
- Store credits are voided
- Return authorizations retain historical data but lose the user association
Reassign Orders Before Deletion
departing = Spree::User.find_by(email: 'departing@company.com')
replacement = Spree::User.find_by(email: 'replacement@company.com')
if departing && replacement
departing.orders.update_all(user_id: replacement.id)
puts "Reassigned #{departing.orders.count} orders"
end
SSO and External Authentication
OmniAuth Integration
Spree supports OmniAuth strategies for social and enterprise SSO:
# Gemfile
gem 'spree_auth_devise'
gem 'omniauth-google-oauth2'
gem 'omniauth-saml'
# config/initializers/devise.rb
Devise.setup do |config|
config.omniauth :google_oauth2,
ENV['GOOGLE_CLIENT_ID'],
ENV['GOOGLE_CLIENT_SECRET'],
scope: 'email,profile'
# SAML for enterprise SSO
config.omniauth :saml,
assertion_consumer_service_url: 'https://your-store.com/users/auth/saml/callback',
idp_sso_service_url: 'https://idp.company.com/sso/saml',
idp_cert_fingerprint: ENV['SAML_CERT_FINGERPRINT'],
name_identifier_format: 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'
end
# app/models/spree/user_decorator.rb
module Spree
module UserDecorator
def self.prepended(base)
base.devise :omniauthable, omniauth_providers: [:google_oauth2, :saml]
end
def self.from_omniauth(auth)
user = Spree::User.find_by(email: auth.info.email)
unless user
user = Spree::User.create!(
email: auth.info.email,
password: Devise.friendly_token[0, 20]
)
end
user
end
end
end
Spree::User.prepend(Spree::UserDecorator)
LDAP Authentication
# Gemfile
gem 'devise_ldap_authenticatable'
# config/initializers/devise.rb
Devise.setup do |config|
config.ldap_logger = true
config.ldap_create_user = true
config.ldap_update_password = false
end
# config/ldap.yml
production:
host: ldap.company.com
port: 636
attribute: uid
base: ou=People,dc=company,dc=com
admin_user: cn=admin,dc=company,dc=com
admin_password: <%= ENV['LDAP_ADMIN_PASSWORD'] %>
ssl: true
Access Audit Checklist
- Review admin users in Settings > Users quarterly (filter by role)
- Run
Spree::User.joins(:spree_roles).where(spree_roles: { name: 'admin' }).pluck(:email)to list all admins - Check for users who have never placed an order but have admin access
- Audit API tokens: rotate
PLATFORM_API_TOKENif team members with access leave - Review
spree_log_entriesfor unusual admin activity - Verify Devise lockout settings (
maximum_attempts,lock_strategy) indevise.rb - Check OmniAuth provider configurations for expired client secrets
- Document all admin access changes in your deployment log