DEV Community

Cover image for Build Secure Shopify Apps with App Bridge
Sulman Baig
Sulman Baig

Posted on • Originally published at sulmanweb.com

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
)
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Lessons Learned

Throughout this project, I learned several valuable lessons:

  1. Session Management: Always use Shopify's session tokens for authentication rather than storing raw access tokens.

  2. Webhook Reliability: Implement idempotency in webhook processing to handle potential duplicate events.

  3. Background Jobs: Use background jobs for any operations that might take more than a few seconds to complete.

  4. Error Handling: Implement comprehensive error handling and logging, especially for webhook processing and API calls.

  5. 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:

  1. Start with a solid authentication and authorization system
  2. Implement webhook handling early in the development process
  3. Use background jobs for long-running tasks
  4. Plan for scalability from the beginning
  5. 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!


Originally published at sulmanweb.com

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more