All Tutorials

Authenticate server-side rendered apps with session tokens using App Bridge and Turbolinks

All Tutorials

Authenticate server-side rendered apps with session tokens using App Bridge and Turbolinks

Authenticate server-side rendered apps with session tokens using App Bridge 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 guide illustrates how to convert your multi-page SSR app to use session token-based authentication with Turbolinks. The sections below describe a step-by-step implementation of the conversion pattern described in Authenticate your app using session tokens.

Requirements

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

You can create a JWT-enabled app using the Shopify App version 14.0.0 gem and running the following generator:

$ rails generate shopify_app --with-session-token

The --with-session-token flag creates an embedded app that is configured to use App Bridge authentication immediately.

Use the following steps to enable Turbolinks on your app.

Steps

  1. Add the turbolinks gem to your Gemfile:

    gem 'turbolinks', '~> 5'
  2. Run bundle install.

  3. Add the turbolinks package to your application.

    $ yarn add turbolinks
  4. 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
  2. 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
  3. 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>
  4. 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
    
      def index
        @shop_origin = current_shopify_domain
      end
    end
  5. Protect the default HomeController by inheriting AuthenticatedController. Update home_controller.rb to match the following code:

    class HomeController < AuthenticatedController
      def index
      end
    end

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 to when a session token has been fetched. In this app, you are 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,
          ...
        } ) %>
    ...
  2. Import the library method getSessionToken from app-bridge-utils in app/javascript/shopify_app/shopify_app.js:

    import { getSessionToken } from "@shopify/app-bridge-utils";
  3. In shopify_app.js, include the following methods that fetch and store a session token every 50 seconds.

    async function retrieveToken(app) {
      window.sessionToken = await getSessionToken(app);
    }
    
    function keepRetrievingToken(app) {
      setInterval(() => {
        retrieveToken(app);
      }, 50000);
    }
  4. 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);
      });
    });
  5. 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);
    
      // Redirect to the requested page
      Turbolinks.visit(data.loadPath);
    
      // Keep retrieving a session token periodically
      keepRetrievingToken(app);
    });

    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 50 seconds or so.

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
  2. Add authentication to the ProductsController by inheriting AuthenticatedController:

    class ProductsController < AuthenticatedController
      def index
        @products = ShopifyAPI::Product.find(:all, params: { limit: 10 })
      end
    end
  3. Create a view for the ProductsController. Update app/views/products/index.html.erb to match the following code:

    <%= link_to 'Back', home_path(shop: @shop_origin) %>
    
    <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>
  4. To satisfy the ShopifyApp::RequireKnownShop concern, add the following method to the before_action filter within AuthenticatedController:

    class AuthenticatedController < ApplicationController
      include ShopifyApp::Authenticated
    
      before_action :shop_origin
    
      def shop_origin
        @shop_origin = current_shopify_domain
      end
    end
  5. Update app/views/home/index.html.erb to include a link to the product index view:

    <%= link_to 'Products', products_path %>

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

Resources

Sample server-side rendered Rails app converted using Turbolinks