Implementation
This page walks through the three GraphQL operations in order and shows a reference integration with Auth.js (NextAuth v5). The same pattern applies to any framework that can run an OIDC/OAuth flow and give you back an ID token.
Required operating mode: SHARED_SECRET.
1. List active providers
Query socialLoginList once on page load (or
whenever you render your login UI) to know which login buttons to show.
{
socialLoginList {
list {
id
name
}
userErrors {
message
}
}
}
idis passed back asproviderIdinsocialLoginVerify. Your application maps this to the Auth.js provider (see below).nameis the back office display name and can change.
Hide or disable any login button whose provider is not in the list.
2. Obtain an ID token from the provider
Run the OAuth/OIDC flow as you would without Centra.
Request the openid scope and whatever provider-specific scopes you need
(email, profile, Apple's name, Facebook's email, etc.).
Pass the ID token you receive to socialLoginVerify.
Do not present the same ID token twice. ID tokens are single-use. A replay is rejected by Storefront API with a generic login error. If you need to retry, obtain a fresh ID token from the provider first.
3. Verify the token
Call socialLoginVerify with the provider ID and the ID token:
mutation SocialLoginVerify(
$providerId: String!
$idToken: String!
) {
socialLoginVerify(
input: {
providerId: $providerId
idToken: $idToken
# optional, see below
overrides: {
firstName: ""
lastName: ""
}
}
) {
nonce
confirmationRequired
userErrors {
message
}
}
}
The response contains:
nonce— pass this tosocialLogin. Valid for 2 minutes, single-use, bound to the current session.confirmationRequired— if true, show a confirmation before callingsocialLogin. See Account linking and confirmation.
When to send overrides
Some providers return the shopper's name outside the ID token or only on first authorization (Apple).
Capture the name on first consent and pass it to socialLoginVerify:
overrides: {
firstName: "Alan"
lastName: "Turing"
}
Overrides are only applied when the ID token does not contain the specific claim and are always safe to send.
4. Confirm (if required)
If confirmationRequired is true, there is already a Centra customer with
this email that is not yet linked to any social provider.
Show the shopper a confirmation:
| An account already exists for user@example.com. Linking with this account will remove your current password. You can reset it any time to sign in with email again. |
|---|
Only proceed to socialLogin with confirmed: true once the shopper has agreed.
5. Complete the login
mutation SocialLogin(
$nonce: String!
$confirmed: Boolean!
) {
socialLogin(
input: {
nonce: $nonce
confirmed: $confirmed
}
# optional, see login mutation
loginOptions: {
applyCustomerMarket: true
applyCustomerPricelist: true
}
) {
loggedIn {
id
email
firstName
lastName
}
selection {
...SelectionFragment
}
session {
...SessionFragment
}
userErrors {
message
}
}
}
The response is the same LoginPayload as the regular
login mutation.
You can use the same post-login logic.
Reference implementation: Auth.js
The example below uses Auth.js v5 (NextAuth) in a Next.js App Router project. Each provider in Auth.js has an id field ("google", "apple", "facebook", or your custom id for generic OIDC); we map that to the Centra provider id by storing it on the provider.
The signIn callback runs server-side after Auth.js has validated the
provider's response. We use it to call socialLoginVerify and socialLogin,
then stash the resulting Centra session/customer on the Auth.js session.
// auth.ts
import NextAuth, { type NextAuthConfig } from "next-auth"
import Google from "next-auth/providers/google"
import Apple from "next-auth/providers/apple"
import Facebook from "next-auth/providers/facebook"
import { centraClient } from "@/lib/centra"
// The id strings must match the provider ids returned by socialLoginList.
const CENTRA_PROVIDER_IDS = {
google: "google-id",
apple: "apple-id",
facebook: "facebook-id",
auth0: "auth0-id",
} as const
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
// clientId and clientSecret can be set in their respective `AUTH_` environment variables:
// https://authjs.dev/getting-started/authentication/oauth
Google({ authorization: { params: { scope: "openid email profile" } } }),
Apple({ authorization: { params: { scope: "name email" } } }),
Facebook({ authorization: { params: { scope: "openid email public_profile" } } }),
Auth0({ authorization: { params: { scope: "openid profile email" } } }),
],
callbacks: {
async signIn({ account, profile, user }) {
if (!account?.id_token) return false
const centraProviderId = CENTRA_PROVIDER_IDS[account.provider as keyof typeof CENTRA_PROVIDER_IDS]
if (!centraProviderId) return false
// 1. Verify the ID token
const verify = await centraClient.mutation(SOCIAL_LOGIN_VERIFY, {
providerId: centraProviderId,
idToken: account.id_token,
// optional overrides from account/profile/user
// overrides: { firstName: "", lastName: "" },
})
if (verify.error) return false
const { nonce, confirmationRequired } = verify.data.socialLoginVerify
// 2. If confirmation is required show confirmation
if (confirmationRequired) {
const confirmed = showConfirmation()
}
// 3. Complete the login
const login = await centraClient.mutation(SOCIAL_LOGIN, {
nonce,
confirmed,
})
if (login.error) return false
// Stash Centra session/customer on the Auth.js session via jwt() callback.
(account as any).centra = login.data.socialLogin
return true
},
// ... additional callbacks ...
},
} satisfies NextAuthConfig)
GraphQL documents:
// lib/centra/documents.ts
import { graphql } from "./codegen"
export const SOCIAL_LOGIN_VERIFY = graphql(/* GraphQL */ `
mutation SocialLoginVerify(
$providerId: String!
$idToken: String!
$overrides: SocialLoginOverrides
) {
socialLoginVerify(
input: {
providerId: $providerId
idToken: $idToken
overrides: $overrides
}
) {
nonce
confirmationRequired
userErrors {
message
}
}
}
`)
export const SOCIAL_LOGIN = graphql(/* GraphQL */ `
mutation SocialLogin(
$nonce: String!
$confirmed: Boolean!
) {
socialLogin(
input: {
nonce: $nonce
confirmed: $confirmed
}
) {
loggedIn {
id
email
firstName
lastName
}
selection {
...SelectionFragment
}
session {
...SessionFragment
}
userErrors {
message
}
}
}
`)
Checklist
Before you ship:
socialLoginListis called and the button set is driven by its response.- Each Auth.js provider is mapped to the
Centra provider id returned by
socialLoginList. - ID tokens are never cached, retried, or stored. One token → one
socialLoginVerifycall. - A confirmation exists for the
confirmationRequired: truecase, and the shopper's agreement is explicit before callingsocialLogin. - Apple name fields are captured on first consent so they can be sent as overrides.
- A fallback to email/password for shoppers whose provider account has no email exists.
- Your identity broker (if any) issues
email_verified: truefor all verified accounts and not for unverified ones. - Provider discovery and JWKS endpoints respond in under one second.