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:
- Your app has the Shopify App gem installed.
- Your app is enabled to use JSON Web Token (JWT) authentication and session tokens.
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.
Enable Turbolinks on your app
Use the following steps to enable Turbolinks on your app.
Steps
Add the
turbolinks
gem to your Gemfile:gem 'turbolinks', '~> 5'
Run
bundle install
.Add the
turbolinks
package to your application.$ yarn add turbolinks
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
Create a
SplashPageController
along with a default index action and view:$ rails generate controller splash_page index
Make
splash_page#index
the default root route for your app. Update the following code in yourroutes.rb
file:Rails.application.routes.draw do root to: 'splash_page#index' ... end
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>
Make the
SplashPageController
behave as the default embedded appHomeController
by updatingapp/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
Protect the default
HomeController
by inheritingAuthenticatedController
. Updatehome_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
andturbolinks:render
events to add anAuthorization
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
Add the following
load_path
parameter toapp/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 tohome_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, ... } ) %> ...
Import the library method
getSessionToken
fromapp-bridge-utils
inapp/javascript/shopify_app/shopify_app.js
:import { getSessionToken } from "@shopify/app-bridge-utils";
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); }
In
shopify_app.js
, add event listeners to theturbolinks:request-start
andturbolinks: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); }); });
In
shopify_app.js
, edit theDOMContentLoaded
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 theload_path
param defined inembedded_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
Generate a
ProductsController
using the Rails generator:$ rails generate controller products index
Add authentication to the
ProductsController
by inheritingAuthenticatedController
:class ProductsController < AuthenticatedController def index @products = ShopifyAPI::Product.find(:all, params: { limit: 10 }) end end
Create a view for the
ProductsController
. Updateapp/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>
Optional. If pages of your app are deep-linked, then add the
ShopifyApp::EnsureAuthenticatedLinks
concern toAuthenticatedController
. The concern is available in Shopify App version 16.1.0:class AuthenticatedController < ApplicationController include ShopifyApp::EnsureAuthenticatedLinks include ShopifyApp::Authenticated end
Optional. To satisfy the
ShopifyApp::RequireKnownShop
concern, add the following method to thebefore_action
filter inAuthenticatedController
:class AuthenticatedController < ApplicationController include ShopifyApp::Authenticated before_action :shop_origin def shop_origin @shop_origin = current_shopify_domain end end
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 theHomeController
using session tokens.
Resources
Sample server-side rendered Rails app converted using Turbolinks