How to implement 'Sign in with Google' for Headless Shopify Stores (React, Next.js, Hydrogen etc.)

I am a developer from North Carolina. I have always been fascinated by the internet, and scince high school I have been trying to understand the magic behind it all. ECU Computer Science Graduate
Shopify, by default, does not have pre-configured methods to authenticate customers via their Google accounts. In a Shopify theme environment, there are numerous apps you could use to accomplish social sign-in. But what about headless stores?!
In this tutorial, I will give you a step-by-step guide on implementing a ‘Sign in with Google’ option to your customer authentication.
This solution will work with ANY React framework (Hydrogen, Next.js, Remix etc.)
Requirements
Storefront API customer authentication
- If you are using the Customer Account API for authentication, see this article to switch to the Storefront API authentication: https://blog.davidwilliford.dev/ditching-customer-account-api-authentication-in-hydrogen
Storefront and Admin API access
- to view & create accounts, and generate access tokens
General Steps
Use Google Cloud Console to generate your Client ID
Enable multipass tokens in your Shopify Admin
Setup npm package
react-oauth/googleMake the client component (renders button and Google popup)
Make the server component (authenticates Google token and creates multipass & Shopify access token)
1: Get Client ID from Google Cloud Console
Enter Google Cloud Console. Make an account and a new project if you have not already.
Then, navigate to APIs & Services → Credentials → Create Credentials
Fill out the form to create your credentials, adding the correct callback URIs and javascript origins.
The callback URI is the url that is visited after user successfully signs in via Google. This url is also sent an id_token.
Make sure to save your client ID, as it will be crucial for the next steps in this tutorial.
2: Enable Multipass Tokens in your Shopify Admin
Within your Shopify Admin, navigate to:
Settings → Classic Customer Accounts → Enable Multipass
Again, make sure to save your Multipass Secret, as it will be used later in the tutorial
3: Setup ‘react-oauth/google’ NPM Package
npm install react-oauth/google
If you are using a CSP (Hydrogen contains one by default), you will need to enable the google account urls in your CSP (Hydrogen’s is in entry.server.jsx):
const {nonce, header, NonceProvider} = createContentSecurityPolicy({
connectSrc: [
'https://accounts.google.com', // add this line
// any other connectSrc items
],
defaultSrc: [
'https://accounts.google.com', // add this line
// any other connectSrc items
],
});
You will also need to wrap your content in the GoogleAuthProvider tag, inputting your Google Cloud client id as a parameter. You also need to include the <script> tag for the google gsi client.
export default function App() {
// whatever logic you have here
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta name="google-site-verification" content="tZeIR-uo2DFULTGGrPpycfZgoligNd9KpFY61czQMFU" />
<Meta/>
<Links pathname={location.pathname} />
</head>
<body className='font-open'>
{/* Wrap content in GoogleAuthProvider */}
<GoogleOAuthProvider clientId="<YOUR_CLIENT_ID>" >
<Elements stripe={stripePromise}>
<Layout {...data}>
<Outlet />
</Layout>
</Elements>
</GoogleOAuthProvider>
<ScrollRestoration nonce={nonce} />
<Scripts nonce={nonce} />
{/* Use google accounts script */}
<script src="https://accounts.google.com/gsi/client" async defer></script>
<LiveReload nonce={nonce} />
</body>
</html>
);
}
4: Create the Client Component
This component will add the ‘Sign in with Google’ button, and allow the popup for users to select their google account.
import React from "react";
import { GoogleLogin } from "@react-oauth/google";
export default function GoogleAuth() {
// on successful google signon, send id token to callback URI
const handleSuccess = (credentialResponse) => {
const idToken = credentialResponse.credential;
if (!idToken) {
console.error("Google ID Token is missing.");
return;
}
window.location.href = `/auth/callback/google?id_token=${idToken}`;
};
// Log error if sign-on fails
const handleError = () => {
console.error("Google Sign-In failed.");
};
return (
<div className="flex justify-center items-center mt-6">
{/* Handles google sign-in popup */}
<GoogleLogin
onSuccess={handleSuccess}
onError={handleError}
theme="outline"
size="large"
text="signin_with"
/>
</div>
);
}
5: Create The Server Component
Make sure the route to this component is the same as the callback URI entered when generating the Google Cloud client id.
This server component:
Gets id_token from google authentication
Gets email using id_token
Creates multipass token for user (using multipassify npm package)
If new account, create a new customer account with this email via Admin API
Use multipass to generate customerAccessToken from Storefront API
Redirects user to account page once complete
import Multipassify from 'multipassify';
import { Buffer } from 'buffer-polyfill';
import { redirect } from '@shopify/remix-oxygen';
import crypto from 'crypto';
globalThis.Buffer = Buffer;
// Creates correctly formated date for multipass field
function generateCreatedAt() {
const now = new Date();
const offsetMinutes = now.getTimezoneOffset();
const offsetHours = Math.floor(Math.abs(offsetMinutes) / 60);
const offsetRemainder = Math.abs(offsetMinutes) % 60;
const offsetSign = offsetMinutes > 0 ? '-' : '+';
const formattedOffset = `${offsetSign}${String(offsetHours).padStart(2, '0')}:${String(offsetRemainder).padStart(2, '0')}`;
return now.toISOString().replace('Z', formattedOffset);
}
function generateSecurePassword() {
// randomly generates secure passsword
}
export const loader = async ({ request, context }) => {
// Environment variables
const STORE_DOMAIN = await context.env.PUBLIC_STORE_DOMAIN;
const ADMIN_API_TOKEN = context.env.SHOPIFY_ADMIN_ACCESS_TOKEN;
// Get incoming id_token from URL param (added by Google)
const url = new URL(request.url);
const idToken = url.searchParams.get("id_token");
if (!idToken) {
throw new Error("ID Token is missing.");
}
// Validate the Google ID Token
const googleResponse = await fetch(`https://oauth2.googleapis.com/tokeninfo?id_token=${idToken}`);
const googleData = await googleResponse.json();
if (!googleResponse.ok || !googleData.email) {
throw new Error("Failed to validate Google ID Token.");
}
// Get email from id_token
const { email } = googleData;
// Get multipass secret from env
const multipassSecret = context.env.MULTIPASS_SECRET;
if (!multipassSecret) {
throw new Error("Multipass secret is missing from the environment variables.");
}
// Create multipass token
const createdAt = generateCreatedAt();
const multipassify = new Multipassify(multipassSecret); // create multipass
const multipassPayload = { email, created_at: createdAt };
const multipassToken = multipassify.encode(multipassPayload); // encode multipass
// Check if customer exists
const customerSearchResponse = await fetch(`https://${STORE_DOMAIN}/admin/api/2023-10/customers/search.json?query=email:${email}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Shopify-Access-Token': ADMIN_API_TOKEN,
},
});
const customerSearchResult = await customerSearchResponse.json();
if (!customerSearchResponse.ok) {
throw new Error(`Error finding account: ${JSON.stringify(result.errors || result)}`);
}
const customerExists = customerSearchResult?.customers.length > 0;
// If customer doesn't exist
if (!customerExists) {
// Auto-generate secure password (however you want to do this)
// Not showing my password generation due to security reasons
const password = generateSecurePassword();
// Create a new customer using the Admin API, if customer does not exist
const createCustomerResponse = await fetch(`https://${STORE_DOMAIN}/admin/api/2023-10/customers.json`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Shopify-Access-Token': ADMIN_API_TOKEN,
},
body: JSON.stringify({
customer: {
email,
password,
password_confirmation: password,
},
}),
});
const createCustomerData = await createCustomerResponse.json();
if (!createCustomerResponse.ok || createCustomerData.errors) {
throw new Error(`Failed to create customer: ${JSON.stringify(createCustomerData.errors || createCustomerData)}`);
}
}
// Authenticate user and generate customerAccessToken using Multipass
const tokenMutation = await context.storefront.mutate(
`
mutation customerAccessTokenCreateWithMultipass($multipassToken: String!) {
customerAccessTokenCreateWithMultipass(multipassToken: $multipassToken) {
customerAccessToken {
accessToken
expiresAt
}
customerUserErrors {
code
field
message
}
}
}
`,
{ variables: { multipassToken } }
);
const { customerAccessToken, customerUserErrors } = tokenMutation?.customerAccessTokenCreateWithMultipass || {};
if (customerUserErrors && customerUserErrors.length > 0) {
throw new Error(
`Failed to create Customer Access Token: ${JSON.stringify(customerUserErrors)}`
);
}
if (!customerAccessToken) {
throw new Error("Failed to generate Customer Access Token.");
}
// Adding access token to session
// Could be different for you, depending on how you are storing & validating tokens
await context.session.set("customerAccessToken", customerAccessToken.accessToken);
// redirect to account page
return redirect("/account", {
headers: {
"Set-Cookie": await context.session.commit(),
},
});
};
Note I am using the buffer npm package under the alias buffer-polyfill . When I try to use buffer without an alias, I get an error since Hydrogen does not use the Node environment. AKA, ignore this if you are not deploying to Oxygen environment
To do this, add this line to your package.json dependencies object and run npm install
buffer-polyfill: "npm:buffer@^6.0.3"
Also with crypto , I have to edit my remix.config file to use crypto on the client and server. Here is the fields I added to my module.exports:
serverNodeBuiltinsPolyfill: {
modules: {
crypto: true, // Provide a JSPM polyfill
},
},
browserNodeBuiltinsPolyfill: {
modules: {
crypto: true,
}
},
Conclusion
Test and tinker with your solution so that it works for your authentication process for your store. Hopefully this can help you, and if not, please feel free to leave a comment or reach out.
Thanks for reading!




