Storefront API authentication and the Web checkout

22 4 7


We're using the Storefront API for our shop. Customers login to the storefront using the `customerAccessTokenCreate` mutation. We then associate the checkout with the customer using the `checkoutCustomerAssociateV2` mutation. However when we direct the user to the web checkout, they're not authenticated.

If they click the login link, they're taken to the custom domain login page where they can authenticate using the themes engine store. This is not ideal as they have to authenticate twice.

From spelunking through the forums and various Github issues, this appears to be the case because of valid security concerns. Is this still the case?

I am also unsure what's the purpose of `checkoutCustomerAssociateV2` and `customerAccessTokenCreate` if when you come to the most important part of most shops: the checkout, you're asked to login again.

What's the recommended implementation for the Storefront API? Should we be using the Themes Engine for all authenticated views (account management, etc) and only use it to build the Storefront API to build the anonymous parts of our storefront?



4 0 0

Hello @martinfotp ,
I am in your same situation, did you find a workaround about this ?

Thanks in advance

22 4 7

I believe that the solution is Multipass.

We came to the conclusion that we mainly cared about a headless storefront and less so about account management and authentication. Our solution without Multipass was to use the theme for authentication and account management.

Elevar built a headless site for StriVectin like this and there’s a public write up here:

We have the Shopify store on a sub domain of our headless site so that we can share cookies for analytics and we also set an “authenticated” cookie from the themes engine to tweak menus on our storefront and pass to analytics. We have a minimal theme with a redirect to our headless site for shop URLs. We use a JS redirect in the head (required to pass query string params as Liquid doesn’t have access to them), head meta redirect fallback after the JS (no query string params) and finally a 500ms delayed with css user message with redirect link in case those both fail. It’s actually rather seamless experience.

A lot of the Shopify and App Store ecosystem make assumptions about shop URLs so the added advantage of the redirects theme is that product page URLs that assume you use the theme still work. We’re also using ReCharge and it was a pain to work with their account subscription management APIs with a headless site so this method allows us to just use their out of the box account management.


4 0 0

thank you so much for you answer @martinfotp 

I believe the same about Multipass, sadly Shopify Plus plan is not a viable option for starters stores.

I'm gonna read the article, and dig about redirect theme, thank you so much.

By now
GraphQL setup docs for getting products and built the PDP pages, home and collection pages is quite good.

Then the official docs start to be a little painful about Customer auth ( ogin etc.. ) and checkout process.

22 4 7

This is an accepted solution.

No worries Hector

We used this minimal theme as a starting point:

It uses webpack so the build process is very familiar to us. We have a CircleCI deployment pipeline setup. 

The relevant parts of our `theme.liquid` redirect logic is as follows (I've stripped everything else):

<!doctype html>
  <title>{{ page_title }}</title>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <meta name="description" content="{{ page_description | escape }}">
  <link rel="canonical" href="{{ canonical_url }}">
  <meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">

  {%- comment -%}
  Detect what 'mode' the page is in. We cannot auto redirect under certain modes.
  {%- endcomment -%}
  {%- if request.design_mode -%}
    {%- assign page_mode = "design" -%}
  {%- elsif content_for_header contains "previewBarInjector.init();" -%}
    {%- assign page_mode = "preview" -%}
  {%- else -%}
    {%- assign page_mode = "live" -%}
  {%- endif -%}

  {%- comment -%}
  ## Headless router

  Re-route to the headless store by assigning the redirect path to `headless_redirect_path`.

  If a path is not assigned to then we will render the liquid template.
  {%- endcomment -%}
  {%- case request.page_type -%}
  {% when "404" %}
  {% when "article" %}
  {% when "blog" %}
  {% when "cart" %}
  {% assign headless_redirect_path = "/cart" %}
  {% when "collection" %}
  {% assign headless_redirect_path = "" %}
  {% when "list-collections" %}
  {% assign headless_redirect_path = "" %}
  {% when "customers/account" %}
  {% when "customers/activate_account" %}
  {% when "customers/addresses" %}
  {% when "customers/login" %}
  {% when "customers/order" %}
  {% when "customers/register" %}
  {% when "customers/reset_password" %}
  {% when "gift_card" %}
  {% when "index" %}
  {% assign headless_redirect_path = "" %}
  {% when "page" %}
  {% when "password" %}
  {% when "product" %}
  {% assign headless_redirect_path = "/products/" | append: handle %}
  {% when "search" %}
  {% assign headless_redirect_path = "" %}
  {% else %}
  {%- endcase -%}

  {%- assign headless_origin = settings.headless_origin -%}
  {%- assign headless_redirect_base_url = settings.headless_redirect_base_url -%}

  {%- comment -%} Construct the full headless path {%- endcomment -%}
  {%- if headless_redirect_path and settings.headless_redirect_enabled == true -%}
    {%- assign headless_redirect_url = headless_origin | append: headless_redirect_base_url | append: headless_redirect_path -%}
  {% endif %}

  {%- comment -%}We attempt JS redirect, if that fails, then a meta redirect with a fallback to a link.{%- endcomment -%}
  {%- if headless_redirect_url -%}
    {%- if page_mode == "live" -%}
      <meta content="noindex" name="robots">
        (function (){
          var href = {{ headless_redirect_url | json }};
          if ( {
            href +=;
          if (window.location.hash) {
            href += window.location.hash;
          window.location.href = href;
        #headless-manual-redirect {
            opacity: 0;
            animation: delayFadeIn 500ms;
            animation-delay: 2s;
            animation-fill-mode: forwards;

        @keyframes delayFadeIn {
            from { opacity: 0; }
            to { opacity: 1; }
      <meta content="1;url={{ headless_redirect_url }}" http-equiv="refresh">
    {%- endif -%}
  {%- else -%}
    {% comment %} Additional head for Theme Engine pages here... {% endcomment %}
    {{ content_for_header }}
  {%- endif -%}
<body class="site">
  {%- if headless_redirect_url -%}
    style="align-items: center; display: flex; flex-direction: column; height: 100vh; justify-content: center; width: 100%;">
    <h1>{{ 'general.headless_redirect.title' | t }}</h1>
      <a href="{{ headless_redirect_url }}">{{ '' | t }}</a>
    {%- if page_mode != "live" %}<em>auto-redirect from {{ headless_redirect_path }} to {{ headless_redirect_url }} skipped in {{ page_mode }} mode</em>{%- endif %}
  {%- else -%}
    {%- section 'site-header' -%}

    <main role="main" class="site-content">
      {{ content_for_layout }}

    {%- section 'site-footer' -%}
  {%- endif -%}



 Good luck!