All Tutorials

Authenticate server-side rendered embedded apps using Rails and Turbolinks

All Tutorials

Authenticate server-side rendered embedded apps using Rails and Turbolinks

Authenticate server-side rendered embedded apps using Rails and Turbolinks

Turbolinks is a JavaScript library that makes your app behave as if it were a single-page app.

If you have a multi-page server-side rendered (SSR) app and you want to use session token-based authentication, then you can still use session tokens by converting your app to use Turbolinks. You can do this even if you're unsure or not yet ready to convert your app to a single-page app.

This tutorial shows you how to convert your multi-page SSR app to use session token-based authentication with Turbolinks.

Requirements

Your app must meet the following requirements to complete this tutorial:

The Shopify App gem version 17.0.5 and higher creates a JWT-enabled app by default when you run this terminal command:

$ rails generate shopify_app

Conversion pattern

To use session tokens with your multi-page app using Turbolinks, we suggest implementing the recommended conversion pattern:

  1. Create an unauthenticated controller that renders a splash page when a user visits your app.

    The splash page communicates to users that your app is loading.

  2. Use the splash page to do the following:

    • Create an App Bridge instance.
    • Retrieve and cache a session token within your app client.
    • Install event listeners to set an "Authorization": "Bearer <session token>" request header on the following events:
      • turbolinks:request-start
      • turbolinks:render
  3. Install a timed event that continues to retrieve and cache session tokens every two seconds.

    This ensures that your session tokens are always valid.

  4. Use Turbolinks to navigate to your app's authenticated home page or resource.

Use the following steps to enable Turbolinks on your app.

Steps

  1. Add the turbolinks gem to your Gemfile:
   gem 'turbolinks', '~> 5'
  1. Run bundle install.

  2. Add the turbolinks package to your application.

   $ yarn add turbolinks
  1. If your app uses webpack to manage its manifest files, then add the following line to app/javascript/packs/application.js:
   require("turbolinks").start();

Create a splash page

Your splash page is used to indicate that your app has begun to fetch a session token. When your app has the token, it should navigate the user to the main view. The main view might contain protected or authenticated resources.

Steps

  1. Create a SplashPageController along with a default index action and view:
   $ rails generate controller splash_page index
  1. Make splash_page#index the default root route for your app. Update the following code in your routes.rb file:
   Rails.application.routes.draw do
     root to: 'splash_page#index'
     ...
   end
  1. Indicate a loading status in your splash page index view. Update app/views/splash_page/index.html.erb to match the following code:
   <p>Loading...</p>
  1. Make the SplashPageController behave as the default embedded app HomeController by updating app/controllers/splash_page_controller.rb to match the following code:
   class SplashPageController < ApplicationController
     include ShopifyApp::EmbeddedApp
     include ShopifyApp::RequireKnownShop
     include ShopifyApp::ShopAccessScopesVerification

     def index
       @shop_origin = current_shopify_domain
     end
   end
  1. Protect the default HomeController by inheriting AuthenticatedController. Update home_controller.rb to match the following code:
   class HomeController < AuthenticatedController
     def index
       @shop_origin = current_shopify_domain
     end
   end
  1. To handle changes in access scopes requested by your app, add the following configuration to /app/config/initializers/shopify_app.rb. For more information on handling changes in access scopes, refer to the Shopify App gem.

    ShopifyApp.configuration.reauth_on_access_scope_changes = true

Fetch and store session tokens

When users visit the app for the first time, you can use Javascript to complete the following tasks on the splash page:

  • Create an App Bridge instance.

  • Fetch a session token and cache it.

  • Install event listeners on the turbolinks:request-start and turbolinks:render events to add an Authorization request header.

  • Install event listeners to add an Authorization request header on the following events:

    • turbolinks:request-start
    • turbolinks:render
  • Use Turbolinks to navigate to the HomeController.

Steps

  1. Add the following load_path parameter to app/views/layouts/embedded_app.html.erb. This parameter is used by Turbolinks to navigate back to this app when a session token has been fetched. In the following example, you're navigating to home_path by default:
   ...
       <%= content_tag(:div, nil, id: 'shopify-app-init', data: {
         api_key: ShopifyApp.configuration.api_key,
         shop_origin: @shop_origin || (@current_shopify_session.domain if @current_shopify_session),
         load_path: params[:return_to] || home_path,
         ...
       } ) %>
   ...
  1. Import the library method getSessionToken from app-bridge-utils in app/javascript/shopify_app/shopify_app.js:
   import { getSessionToken } from "@shopify/app-bridge-utils";
  1. In shopify_app.js, include the following methods that fetch and store a session token every two seconds:
   const SESSION_TOKEN_REFRESH_INTERVAL = 2000; // Request a new token every 2s

   async function retrieveToken(app) {
     window.sessionToken = await getSessionToken(app);
   }

   function keepRetrievingToken(app) {
     setInterval(() => {
       retrieveToken(app);
     }, SESSION_TOKEN_REFRESH_INTERVAL);
   }
  1. In shopify_app.js, also include the following helper method and replace '/home' with your app's home_path. The helper method navigates your app using Turbolinks. It does this by determining whether the navigation is made on the initial load of your app.
   function redirectThroughTurbolinks(isInitialRedirect = false) {
     var data = document.getElementById("shopify-app-init").dataset;
     var validLoadPath = data && data.loadPath;
     var shouldRedirect = false;

     switch (isInitialRedirect) {
       case true:
         shouldRedirect = validLoadPath;
         break;
       case false:
         shouldRedirect = validLoadPath && data.loadPath !== "/home";
         break;
     }
     if (shouldRedirect) Turbolinks.visit(data.loadPath);
   }
  1. In shopify_app.js, add event listeners to the turbolinks:request-start and turbolinks:render events. Set an "Authorization": "Bearer <session token>" header during these events:
   document.addEventListener("turbolinks:request-start", function (event) {
     var xhr = event.data.xhr;
     xhr.setRequestHeader("Authorization", "Bearer " + window.sessionToken);
   });

   document.addEventListener("turbolinks:render", function () {
     $("form, a[data-method=delete]").on("ajax:beforeSend", function (event) {
       const xhr = event.detail[0];
       xhr.setRequestHeader("Authorization", "Bearer " + window.sessionToken);
     });
   });
  1. In shopify_app.js, edit the DOMContentLoaded event listener to add the following instructions:
   document.addEventListener("DOMContentLoaded", async () => {
     var data = document.getElementById("shopify-app-init").dataset;
     var AppBridge = window["app-bridge"];
     var createApp = AppBridge.default;
     window.app = createApp({
       apiKey: data.apiKey,
       shopOrigin: data.shopOrigin,
     });

     var actions = AppBridge.actions;
     var TitleBar = actions.TitleBar;
     TitleBar.create(app, {
       title: data.page,
     });

     // Wait for a session token before trying to load an authenticated page
     await retrieveToken(app);

     // Keep retrieving a session token periodically
     keepRetrievingToken(app);

     // Redirect to the requested page when DOM loads
     var isInitialRedirect = true;
     redirectThroughTurbolinks(isInitialRedirect);

     document.addEventListener("turbolinks:load", function (event) {
       redirectThroughTurbolinks();
     });
   });

After a session token is retrieved, Turbolinks.visit(data.loadPath) visits the load_path param defined in embedded_app.html.erb.

Your app continues to retrieve session tokens every two seconds.

Request authenticated resources

When a user visits your app, they should now briefly see a loading screen before they're taken to the Home view of your app. The Home view is authenticated by the HomeController.

For demo purposes, we have created two additional authenticated controllers: ProductsController and WidgetsController. The following steps describe how to create the ProductsController and add a navigation link to the Home view.

Steps

  1. Generate a ProductsController using the Rails generator:
   $ rails generate controller products index
  1. Add authentication to the ProductsController by inheriting AuthenticatedController:
   class ProductsController < AuthenticatedController
     def index
       @products = ShopifyAPI::Product.find(:all, params: { limit: 10 })
     end
   end
  1. Create a view for the ProductsController. Update app/views/products/index.html.erb to match the following code:
   <%= link_to 'Back', home_path %>

   <h2>Products</h2>

   <ul>
     <% @products.each do |product| %>
       <li><%= link_to product.title, "https://#{@current_shopify_session.domain}/admin/products/#{product.id}", target: "_top" %></li>
     <% end %>
   </ul>
  1. Add the ShopifyApp::EnsureAuthenticatedLinks concern to AuthenticatedController to authenticate pages of your app that are deep-linked. This concern is available in Shopify App version 17.0.5:
   class AuthenticatedController < ApplicationController
     include ShopifyApp::EnsureAuthenticatedLinks
     include ShopifyApp::Authenticated
   end
  1. Update app/views/home/index.html.erb to include a link to the product index view:
   <%= link_to 'Products', products_path(shop: @shop_origin) %>

Your app can now access the authenticated ProductsController from the HomeController using session tokens.

Mark shop records as uninstalled

To ensure OAuth continues to work with session tokens, your app must update its shop records in the event a shop uninstalls your app. The app can receive notifications of these events by subscribing to the app/uninstalled webhook. For more information, refer to Mark a shop record as uninstalled using webhooks.

Resources

Sample server-side rendered Rails app converted using Turbolinks

Next steps

  • Make authenticated requests using Axios.
  • Use helper functions to fetch a session token from App Bridge and include them in requests being made to the app backend.
  • Learn how to build a Shopify app with Rails 6, React, and App Bridge authentication.