# Customer Accounts

There are two methodologies for setting up accounts. Choose the best one that works for you.

  1. Shopify Hosted Account Solution
    • Keep account logic in liquid and hosted on Shopify
    • Will require installing a snippet
  2. Integrated Account Solution
    • Create an unified account experience within your application utilizing the Shopify Storefront API, Multipass, and Netlify Functions.
    • Will be a bit more complex, but provide complete control of the customer account experience.

# Shopify Hosted Solution

Keep account logic in liquid and hosted on Shopify

# Install Account Snippet

Include the following snippet in customers/account.liquid:

<script src="https://cdn.jsdelivr.net/npm/[email protected]/src/js.cookie.min.js"></script>

<script src="https://cdn.jsdelivr.net/npm/[email protected]/browser.js"></script>

<script>
  var userID = '{{customer.id}}'
  var customerEmail = '{{customer.email}}'
  var userData = { userID: userID, customerEmail: customerEmail }

  Cookies.set('user-data', JSON.stringify(userData), { expires: 30 })
</script>

# Integrated Account Solution

Create Account Functionality with Serverless APIs.

# Summary

We want a simple solution to handling customer accounts that doesn't involve maintaining code in Shopify's Theme. Let's explore a headless accounts setup that gives us all of the basic account functionality that we need.

If you'd like to see an example project that follows the steps described below, please refer to the Nuxt Shopify Accounts example project in the nacelle-launch-tests repo.

# Account Page Actions

example structure:

├── Page
│   └── resource
│       └── ACTION
├── Account Page
│   ├── customer                        # requires customerAccessToken
│   │   ├── READ
│   │   └── UPDATE
│   ├── orders                          # requires customerAccessToken
│   │   └── READ
│   └── addresses                       # requires customerAccessToken
│       ├── READ
│       ├── UPDATE
│       ├── CREATE
│       └── DELETE
├── Login Page
│   └── customerAccessToken
│       └── CREATE                      # create customerAccessToken w/ email and password
├── Register Page
│   └── customer
│       └── CREATE                      # create customer account w/ email and password
├── Recover Password Page
│   └── resetToken
│       └── CREATE                      # sends password recovery email to customer
└── Reset Password Page
    └── customer
        └── UPDATE                      # updates customer password with resetToken from recovery email

# Prerequisites

# Setup

  1. add a few items to our .env file:
SHOPIFY_MULTIPASS_SECRET="15b40af008bfad7b5dfbf36c389abf70"
MYSHOPIFY_DOMAIN="nacelle-accounts.myshopify.com"
SHOPIFY_STOREFRONT_ACCESS_TOKEN="789bfb8d1376a93439b27953b60ac357"
SHOPIFY_CUSTOM_DOMAIN="nacelle.commercejam.com"
  1. We'll need to expose these to various parts of our application through our nuxt.config.js:

Note that SHOPIFY_STOREFRONT_ACCESS_TOKEN is your store's Storefront API Token.

{
   env: {
    // ...other environment variables,
    myshopifyDomain: process.env.MYSHOPIFY_DOMAIN,
    shopifyToken: process.env.SHOPIFY_STOREFRONT_ACCESS_TOKEN,
  },
  nacelle: {
    // ...other Nacelle config,
    customEndpoint: process.env.NACELLE_CUSTOM_ENDPOINT,
    myshopifyDomain: process.env.MYSHOPIFY_DOMAIN,
    shopifyCustomDomain: process.env.SHOPIFY_CUSTOM_DOMAIN,
    shopifyToken: process.env.SHOPIFY_STOREFRONT_ACCESS_TOKEN,
  }
}
  1. The following three dependencies need to be installed:
  • npm install cookie-universal-nuxt

For more information about these dependencies, check out their repositories:

  1. Cookie Universal Nuxt requires an update to nuxt.config.json. Add to modules array.
{
  modules: [
    // ...other modules,
    'cookie-universal-nuxt',
  ]
}
  1. Plugins

We'll need to add authOnLoad.js plugin to `nuxt.config.js:

plugins: [
  { src: '~/plugins/authOnLoad.js', ssr: false }
],

# Serverless Functions

To use Multipass and the address form for account pages this project relies on two packages:

Both of these packages are large and can add a lot to your client bundle, so using them only in serverless functions keeps the client a little more lightweight. But if you want another reason, it also keeps your Multipass secret outside of client code.

This project includes two different folders for serverless functions:

Those folder names are useful conventions for using serverless with these different platforms. Both of those folders use shared code in ./accounts related to making use of account data.

Specify your serverless endpoint in your .env, for example:

SERVERLESS_ENDPOINT='/api'

ℹ️ NOTE: For a Netlify configuration, it's convenient to use a netlify.toml to redirect the requests to the /functions to point to /api.

When testing your serverless functions locally make sure to use the respective platform's CLI (Vercel CLI (opens new window), Netlify CLI (opens new window)). NPM scripts for running the project with these CLI's are provided in the package.json.

# Other Code Additions

Dir Description
components/account/* (opens new window) Account components
gql/* (opens new window) exports GraphQl queries and related utility functions.
middleware/* (opens new window) SPA style route guards. Included on certain pages
pages/account/* (opens new window) Account Page Templates
plugins/authOnLoad.js (opens new window) Router on ready plugin for auth middleware
static/account-head.js (opens new window) On page load guard clause for better UX
static/email-referrer-head-check.js (opens new window) On page load guard clause for better UX when being redirected from emails
store/account.js (opens new window) Account related Actions and Mutations

# File Modifications

File Description
components/CartFlyoutCheckoutButton.vue (opens new window) intercept checkout url and modify with custom domain
layouts/default.vue (opens new window) add read token action to mounted hook
nuxt.config.js (opens new window) add nuxt-universal-cookie module and environment variable additions

# Shopify Email Notifications

  1. Password Recovery and Reset

    • During the password recovery flow, an email is sent to the customer with a link to the reset their password. We'll want to make sure to edit this link to point towards our app instead of the Shopify hosted domain.

    • We are using using query parameters vs url parameters since we are using static site generation and can't handle dynamic routes.

    • The url path will appear like:

      • /account/reset?id=2864558604347&token=a000add20a69bb53954976edd74870a4-1581119357

      versus:

      • /account/reset/2864558604347/a000add20a69bb53954976edd74870a4-1581119357
{% comment %}
  Edit Customer Account Reset (/admin/email_templates/customer_account_reset/edit)
  ----
  Old tag:
  <a href="{{ customer.reset_password_url }}" class="button__text">Reset your password</a>
{% endcomment %}
{% assign url_parts = customer.reset_password_url  | split: '/' %}
<a href="http://domain.com/account/activate?id={{url_parts[5]}}&token={{url_parts[6]}}" class="button__text">Reset your password</a>
  1. Account Activate

    • The merchant can send an account activation email with a link to the storefront to create a password and activate their account. We'll want to make sure to edit this link to point towards our app instead of the Shopify hosted domain.

    • We are using using query parameters vs url parameters since we are using static site generation and can't handle dynamic routes.

    • The url path will appear like:

      • /account/activate?id=2864558604347&token=a000add20a69bb53954976edd74870a4-1581119357

      versus:

      • /account/activate/2864558604347/a000add20a69bb53954976edd74870a4-1581119357
{% comment %}
  Edit Customer Account Invite (/admin/email_templates/customer_account_activate/edit)
  ----
  Old tag:
  <a href="{{ customer.account_activation_url }}" class="button__text">Activate Account</a>
{% endcomment %}
{% assign url_parts = customer.account_activation_url  | split: '/' %}
<a href="http://domain.com/account/activate?id={{url_parts[5]}}&token={{url_parts[6]}}" class="button__text">Activate Account</a>

# Social Login

If you want to add OAuth-style social login you will also need some additional pieces. However, if you don't need social login for your store, then the pieces mentioned below can be removed.

We will need a backend service to handle some of these actions -- again here is where serverless saves the day.

One serverless function will be served:

  • auth.js

Five routes will be exposed:

  • auth/google
  • auth/google/callback
  • auth/facebook
  • auth/facebook/callback
  • auth/status

This are provided in the accounts directory.

accounts
├── app
│   ├── app.js                     # exports instance of App class which is an express app
│   └── routes.js                  # declares and exports routes that will accessible to frontend.
├── controllers
│   └── auth.controller.js         # a set of functions that handles payloads, state, callbacks, etc.
├── utils
│   ├── logger.js                  # a custom logger
│   ├── passport.js                # handles our auth strategies
│   └── secrets.js                 # A secrets store that exposes environment variables to our app
└── auth.js                        # Root level files represent lambdas and export a handler function

# Add a few new items to our .env file

BASE_URL="http://localhost:8888"
NACELLE_PASSPORT_SECRET="makeitso"
FACEBOOK_APP_ID="123423453456"
FACEBOOK_APP_SECRET="123423453456"
GOOGLE_CLIENT_ID="123423453456.apps.googleusercontent.com"
GOOGLE_CLIENT_SECRET="123423453456"

# Social App Setup

  1. In order to use Facebook authentication with passport-facebook, you must first create an app at Facebook Developers (opens new window). When created, an app is assigned an App ID and App Secret. Your application must also implement a redirect URL, to which Facebook will redirect users after they have approved access for your application. (ie. https://<your-domain>/api/auth/facebook/callback)

    • Note facebook assumes to whitelist a localhost callback, so explicitly adding one is not necessary while the app status is set to "In Development"
  2. Before using passport-google-oauth20, you must register an application with Google. If you have not already done so, a new project can be created in the Google Developers Console (opens new window). Your application will be issued a client ID and client secret, which need to be provided to the strategy. You will also need to configure a redirect URI which matches the route in your application. (ie. https://<your-domain>/api/auth/google/callback )

    • Note google will require a callback for development and production (ie. http://localhost:8888/.netlify/functions/auth/google/callback)