Build Secure Shopify Apps with App Bridge

As a developer working with Shopify's ecosystem, I recently built a multi-tenant SaaS application that synchronizes customer data between Shopify stores and external services. In this article, I'll share my experience and technical insights into creating a secure, scalable Shopify app using App Bridge.
Project Overview
Our application needed to:
- Handle multiple Shopify stores (multi-tenancy)
- Process customer data securely
- Provide a seamless embedded experience
- Manage OAuth flows and webhooks
- Handle billing subscriptions
Let's dive into how we accomplished these requirements using Rails 8 and Shopify's App Bridge.
Setting Up the Foundation
First, we set up our Rails application with the necessary Shopify integrations. Here's how our initial configuration looked:
ShopifyApp.configure do |config|
config.embedded_app = true
config.scope = "read_customers,write_customers"
config.after_authenticate_job = false
config.api_version = "2024-10"
config.shop_session_repository = "Shop"
config.webhooks = [
{ topic: "app/uninstalled", address: "webhooks/app_uninstalled" },
{ topic: "customers/create", address: "webhooks/customers_create" },
{ topic: "customers/update", address: "webhooks/customers_update" }
]
end
Multi-tenant Data Model
Our core data model revolves around the Shop model, which handles multi-tenancy:
class Shop < ActiveRecord::Base
include ShopifyApp::ShopSessionStorageWithScopes
has_many :customers, dependent: :destroy
encrypts :api_key, deterministic: true
end
Embedded App Architecture
One of the key aspects was creating a seamless embedded experience. We achieved this through our layout configuration:
<%# app/views/layouts/embedded_app.html.erb %>
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://unpkg.com/@shopify/app-bridge@3.7.9"></script>
<script>
var AppBridge = window['app-bridge'];
var createApp = AppBridge.default;
var app = createApp({
apiKey: "<%= ShopifyApp.configuration.api_key %>",
host: "<%= @host %>",
forceRedirect: true
});
</script>
</head>
<body>
<%= yield %>
</body>
</html>
Secure Authentication Flow
We implemented authenticated controllers to ensure secure access:
class AuthenticatedController < ApplicationController
include ShopifyApp::EnsureHasSession
before_action :ensure_store_settings
private
def ensure_store_settings
redirect_to settings_path unless current_shop.setup_completed?
end
end
Webhook Processing
Handling webhooks securely was crucial for our app. Here's our webhook processing implementation:
class WebhooksController < ApplicationController
include ShopifyApp::WebhookVerification
def customers_create
shop = Shop.find_by(shopify_domain: params[:shop_domain])
if shop
shop.with_shopify_session do
process_customer_data(params)
end
end
head :ok
end
private
def process_customer_data(data)
CustomerProcessingJob.perform_later(
shop_id: shop.id,
customer_data: data
)
end
end
Background Job Processing
We used Solid Queue for reliable background processing:
class CustomerProcessingJob < ApplicationJob
def perform(shop_id:, customer_data:)
shop = Shop.find(shop_id)
shop.with_shopify_session do
customer = shop.customers.find_or_initialize_by(
shopify_customer_id: customer_data["id"]
)
customer.update!(
email: customer_data["email"],
first_name: customer_data["first_name"],
last_name: customer_data["last_name"]
)
end
end
end
Billing Integration
We implemented Shopify's billing API to handle subscriptions:
config.billing = ShopifyApp::BillingConfiguration.new(
charge_name: "App Subscription",
amount: 4.99,
interval: ShopifyApp::BillingConfiguration::INTERVAL_EVERY_30_DAYS,
trial_days: 7
)
User Interface with Tailwind CSS
We created a clean, responsive interface using Tailwind CSS:
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 pb-12 pt-20">
<div class="sm:flex sm:items-center">
<div class="sm:flex-auto">
<h1 class="text-base font-semibold leading-6 text-gray-900">
Customers
</h1>
<p class="mt-2 text-sm text-gray-700">
Manage your synchronized customers
</p>
</div>
</div>
<%= render "customer_list", customers: @customers %>
</div>
Lessons Learned
Throughout this project, I learned several valuable lessons:
Session Management: Always use Shopify's session tokens for authentication rather than storing raw access tokens.
Webhook Reliability: Implement idempotency in webhook processing to handle potential duplicate events.
Background Jobs: Use background jobs for any operations that might take more than a few seconds to complete.
Error Handling: Implement comprehensive error handling and logging, especially for webhook processing and API calls.
Security: Always encrypt sensitive data and never expose API keys in the frontend.
Conclusion
Building a multi-tenant Shopify app requires careful consideration of security, scalability, and user experience. By leveraging Rails, App Bridge, and modern development practices, we created a robust application that securely handles multiple stores and their data.
The combination of Shopify's App Bridge, Rails 8, and modern tools like Solid Queue and Tailwind CSS provided a solid foundation for building a scalable SaaS application.
Next Steps
If you're building a Shopify app, consider these recommendations:
- Start with a solid authentication and authorization system
- Implement webhook handling early in the development process
- Use background jobs for long-running tasks
- Plan for scalability from the beginning
- Follow Shopify's security best practices
Remember that building a multi-tenant application requires careful consideration of data isolation and security at every level of your application.
Happy Coding!
About the author
Senior Software Engineer with 12 years of expertise in Ruby on Rails and Vue.js, specializing in health, e-commerce, staffing, and transport. Experienced in software development and version analysis.
Comments
Join the discussionNo comments yet. Be the first to comment.





