All Tutorials

Authenticate your app using session tokens

All Tutorials

Authenticate your app using session tokens

Authenticate your app using session tokens

Embedded apps currently rely on 3rd party cookies to authenticate. When the app loads, it sets a cookie in the browser. The app keeps track of the cookie and any other relevant information in a session on its server. The browser sends the cookie to the server with every request.

For privacy reasons, browsers are now restricting the use of 3rd party cookies. This means that you can't use cookies to keep track of sessions of embedded apps.

As an alternative to cookies, use session tokens to authenticate your embedded app.

Session tokens

Session tokens contain information about the merchant who is logged into the Shopify admin. Session token-based authentication does not rely on cookies for embedded apps to authenticate. Instead, your app frontend sends the session token to its backend with every request. The backend then uses the session token to determine the user's identity.

How it works

When your app first loads, it is unauthenticated and serves up the frontend code for your single-page app. The app can render a user interface skeleton or loading screen to the user.

Once the frontend code has loaded, it can call an App Bridge action to get the session token JWT (JSON web token). It can then include the session token in an authorization header when it makes any HTTPS requests to its backend.

The session token is signed using the shared secret so your backend can verify if the request is valid.

Authentication flow using a session token

Request flow using a session token

Anatomy of a session token

Once decoded, the Shopify session token JWT has the following fields:

The values in the header are constant and will never change.

{
  "alg": "HS256",
  "typ": "JWT"
}
  • alg: The algorithm used to encode the JWT.
  • typ: The type header used by session token to declare the media type.

Payload

{
  "iss": "<shop-name.myshopify.com/admin>",
  "dest": "<shop-name.myshopify.com>",
  "aud": "<api key>",
  "sub": "<user ID>",
  "exp": "<time in seconds>",
  "nbf": "<time in seconds>",
  "iat": "<time in seconds>",
  "jti": "<random UUID>",
  "sid": "<session ID>"
}
  • iss: The shop's admin domain.
  • dest: The shop's domain.
  • aud: The API key of the receiving app.
  • sub: The user the session token is intended for.
  • exp: When the session token expires.
  • nbf: When the session token activates.
  • iat: When the session token was issued.
  • jti: A secure random UUID.
  • sid: A unique session ID per user and app.

Example payload

{
 "iss"=>"https://exampleshop.myshopify.com/admin",
 "dest"=>"https://exampleshop.myshopify.com",
 "aud"=>"api-key-123",
 "sub"=>"42",
 "exp"=>1591765058,
 "nbf"=>1591764998,
 "iat"=>1591764998,
 "jti"=>"f8912129-1af6-4cad-9ca3-76b0f7621087",
 "sid"=>"aaea182f2732d44c23057c0fea584021a4485b2bd25d3eb7fd349313ad24c685"
}

Setup

The following section describes the requirements, recommendations, and changes associated with setting up session token authentication in your embedded app.

Requirements

Recommendations

Session token-based authentication works best with a single-page app. Without cookies, every new page load needs to be a two-step process. The first step is an unauthenticated load of the frontend JavaScript. The second step uses the session token to start fetching the merchant data.

If you have a multi-page app, you might be able to convert it to behave as if it were a single-page app using Turbolinks.

Changes

You must make changes to both the backend and frontend to set up session token authentication in your embedded app.

Backend changes

1. Enable a session token or manually decode the session token
2. Allow unauthenticated requests

Update the route that serves the single-page app so that it allows unauthenticated requests.

If the response generated by this route depends on the request being authenticated, remove the data from the response and expose it to the frontend using an authenticated API route. For example, your app might be embedding merchant information in its response.

Add logic to the unauthenticated route to detect if this is the first time the shop is loading your app.

If your app doesn't have a shop token for the shop, then the route should respond with a redirect to send the user to the OAuth login flow. This ensures that the app is installed and receives its shop token.

3. Add middleware detection

Add middleware that detects requests with a session token present and builds a session based on the shop and user information included in the token.

To verify the authenticity of a session token, see Verify the signature.

Frontend changes

Set up custom fetch
  1. Edit the file where you have defined your App Bridge app.

    app.js is the demo app.

  2. Import authenticatedFetch from the @shopify/app-bridge-utils package.

    You'll need version 1.23.0.

  3. If you are using the JavaScript fetch API, replace all instances of fetch in your app with authenticatedFetch.

Set up Apollo Client

If you are using Apollo, then complete the following steps:

  1. When creating the HTTPLink, pass in authenticatedFetch as an option:

    const link = new HTTPLink({
      credentials: 'same-origin',
      fetch: authenticatedFetch(app), // app: App Bridge instance
    });
  2. Pass the link to ApolloClient:

    const client = new ApolloClient({
      link,
      cache,
    });

Enable session token authentication

If your app is built using the Shopify App gem, then complete the following steps to enable session token authentication.

Steps

  1. Configure your app to use session tokens by editing config/initializers/shopify_app.rb and adding the following line:

    config.allow_jwt_authentication = true
  2. Edit the HomeController (app/controllers/home_controller.rb) so that it looks like the following code.

    This makes the controller allow unauthenticated requests by subclassing ApplicationController.

    It includes the ShopifyApp::RequireKnownShop concern so that the controller handles the shop token installation for you.

    In the index action, an instance variable called @shop_origin is defined and set to current_shopify_domain.

    # frozen_string_literal: true
    
    class HomeController < ApplicationController
      include ShopifyApp::EmbeddedApp
      include ShopifyApp::RequireKnownShop
    
      def index
        @shop_origin = current_shopify_domain
      end
    end
  3. Create a new view in app/views/home.index.html.erb and add the following:

    <div
        id="app"
        data-api-key="<%= ShopifyApp.configuration.api_key %>"
        data-shop-origin="<%= @shop_origin %>"
    >
    </div>

Obtain session details manually

If your app isn't built using the Shopify App gem, then use the following steps to help you manually decode the session token (JWT) and verify that it is valid. The session token is signed using the shared secret of your Shopify app.

Steps

  1. Decode the session token, verify the signature, and obtain the headers and payload.
  2. Extract the exp value from the payload.

    Verify that it is in the future.

  3. Extract the nbf value from the payload.

    Verify that it was in the past.

  4. Extract the iss and dest fields from the payload.

    The top level domains should match. The dest field specifies the shops the request originated from. For example, myshop.myshopify.com.

  5. Extract the aud value from the payload.

    Verify that it matches the API key of your app.

  6. Extract the sub value from the payload.

    This is the ID of the user that made the request.

If any of the above steps fail, discard and stop further processing of the request immediately, and respond with an error.

Verify the signature

Use the following steps to verify that the issued token has a valid signature. To verify that the signature is correct, you need to generate a new Base64url-encoded signature using the app’s shared secret.

Session tokens are signed using the HS256 algorithm. This is a symmetric algorithm. The signing key is the shared secret for your Shopify app. A session token is a JWT string with the following structure: <header>.<payload>.<signature>

All three sections are base64 encoded.

Steps

  1. Take the <header>.<payload> portion of the string and hash it with SHA-256.
  2. Sign the string using the HS256 algorithm by using the app’s secret as the signing key.
  3. Base64url-encode the result.
  4. Verify the result is the same as the signature sent with the session token.

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.

To use session tokens with your multi-page app using Turbolinks, we suggest implementing the recommended conversion pattern. For a detailed implementation guide, refer to Use Turbolinks to convert a multi-page SSR app.

Conversion pattern steps

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

    This ensures that your session tokens are always valid.

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

For an implementation guide specific to a Rails app made using the Shopify App gem, refer to the sample multi-page rails app. The sample app provides step-by-step instructions on how to enable and use session tokens with your app using Turbolinks.

Use session tokens with Axios

Axios is a popular promise-based HTTP client. It can be used to transform and intercept HTTP request/response data asynchronously.

To make authenticated requests using Axios, you can use Axios interceptors to append an authenticated session token header prior to each request.

Steps

  1. Install an interceptor to your instance of Axios.
  2. Define a function that your axios instance calls prior to each request.

    This function takes a config object as parameter and returns a config object that is used as configuration for all subsequent requests.

    1. In the function, call getSessionToken() and resolve the token it returns.
    2. Modify the config object to append an “Authorization”: “Bearer token” header to your requests:
      import axios from 'axios';
      import { getSessionToken } from '@shopify/app-bridge-utils';
    
      const instance = axios.create();
      // intercept all requests on this axios instance
      instance.interceptors.request.use(
        function (config) {
          return getSessionToken(window.app)  // requires an App Bridge instance
            .then((token) => {
              // append your request headers with an authenticated token
              config.headers['Authorization'] = `Bearer ${token}`;
              return config;
            });
        }
      );
      // export your axios instance to use within your app
      export default instance;

Use session token helpers directly

The app-bridge-utils library provides helper functions to handle using session tokens for your application. You can use helper functions to fetch a session token from App Bridge and include them in requests being made to the app backend.

Get a session token

The getSessionToken helper retrieves a session token from Shopify. It sets up a subscription on the App Bridge client to listen for the APP::SESSION_TOKEN_RESPOND action and then immediately dispatches APP::SESSION_TOKEN_REQUEST action.

In your app, set up the App Bridge client and import getSessionToken:

import createApp from '@shopify/app-bridge';
import {getSessionToken} from '@shopify/app-bridge-utils';

const app = createApp({
  apiKey: '12345',
});

Where your app requires a session token, call the following code:

const sessionToken = await getSessionToken(app);

getSessionToken returns a Promise, which either resolves with the session token, or rejects with an APP::ERROR::FAILED_AUTHENTICATION error when the session token is undefined.

Authenticate your requests

The authenticatedFetch helper function authenticates your requests using the session token. The function internally gets the session token from App Bridge and passes in the Authorization header to your subsequent fetch requests.

Parameters

  • app: The App Bridge instance.
  • fetchOperation: An optional parameter that allows you to define your own custom fetch wrapper.

The following example illustrates how to use authenticatedFetch with a custom ApolloLink:

import ApolloClient from 'apollo-client';
import {authenticatedFetch} from '@shopify/app-bridge-utils';
import createApp from '@shopify/app-bridge';
import {HttpLink} from 'apollo-link-http';
import {InMemoryCache} from 'apollo-cache-inmemory';

const app = createApp({
  apiKey: '12345',
});

const client = new ApolloClient({
  link: new HttpLink({
    credentials: 'same-origin',
    fetch: authenticatedFetch(app), // ensures all apollo client triggered requests are authenticated
  }),
  cache: new InMemoryCache(),
});

Use your own custom fetch wrapper

You can optionally pass in your own custom fetch wrapper function to the fetchOperation parameter. The app-bridge-utils function (authenticatedFetch) returns your custom fetch wrapper function along with an authenticated Authorization header appended to the request options provided.

import ApolloClient from 'apollo-client';
import {authenticatedFetch} from '@shopify/app-bridge-utils';
import createApp from '@shopify/app-bridge';
import deepMerge from '@shopify/app-bridge/actions/merge';
import {HttpLink} from 'apollo-link-http';
import {InMemoryCache} from 'apollo-cache-inmemory';

// Sample custom fetch wrapper
const yourCustomFetchWrapper = (uri, options) => {
  const aggregateOptions = deepMerge(options, {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
  });
  return fetch(uri, aggregateOptions);
};

const app = createApp({
  apiKey: '12345',
});

const client = new ApolloClient({
  link: new HttpLink({
    credentials: 'same-origin',
    fetch: authenticatedFetch(app, yourCustomFetchWrapper), // ensures your custom fetch wrapper is authenticated
  }),
  cache: new InMemoryCache(),
});

When you're done

Your app should now work using session token authentication. When any network calls are made, you should see the session token being sent in the header:

Resources