Extension authentication

Ask about this Page
Copy for AI
View as Markdown

Let your Project's own extension authenticate shoppers.

A Project can delegate shopper authentication to its own extension instead of configuring an identity provider. Use this when your identity provider isn't a standards-compliant OpenID Connect (OIDC) provider the Commerce Integration Layer can verify directly, for example, one that mints its own tokens with a non-standard issuer or signing scheme. Your extension performs the whole authentication and tells the Commerce Integration Layer who the shopper is.
Your extension becomes an identity authority for the Project: it names an external identity, never a commercetools Customer ID directly, which the Commerce Integration Layer resolves to a Customer the same way the idp grant does.

Sign-in workflow

Sign-in has two parts: the shopper authenticates with your own identity system, then your storefront exchanges the result for a Commerce Integration Layer session.

  1. Your storefront authenticates the shopper through whatever flow your own identity system requires. This step happens entirely between your storefront and your systems; the Commerce Integration Layer isn't involved yet.
  2. Your storefront sends the resulting opaque value to POST /session with grant_type: "extension", as described in Session grants.
  3. The Commerce Integration Layer calls your extension's onResolveLogin hook with that value.
  4. Your extension verifies the shopper's credential however it needs to, and answers with an external identity.
  5. The Commerce Integration Layer maps the identity to a commercetools Customer, creating or updating one keyed on externalId.
  6. The Commerce Integration Layer returns a session token, exactly as it does for any other session grant.
Export two hooks from your bundle's hooks object, both optional:
export const hooks = {
  onResolveLogin,
  onRevalidateSession,
};
HookRequired forAbsence means
onResolveLoginThe extension grant to work at allThe grant is refused — there's no fallback authentication path.
onRevalidateSessionRefreshing an extension-anchored sessionThe session ends when its anchor lapses; it is not extended.

onResolveLogin

Called once, when a shopper signs in with grant_type: extension.
input CilLoginInput {
  code: String!
}

type CilLoginResult {
  externalId: String!
  email: String
  firstName: String
  lastName: String
  phone: String
  anchorToken: String
  expiresIn: Int
}
  • code — the opaque value your storefront collected from your own identity system. The Commerce Integration Layer never inspects it.
  • externalId — required, and must be non-empty. Keys the session's identity, matched against a commercetools Customer's externalId and used to create one on first login.
  • email / firstName / lastName / phone — optional profile fields used to seed the Customer on first login.
  • anchorToken — optional opaque value, for example the upstream token you verified. Sealed into the session; never exposed to the browser or to resolvers, and handed back to you verbatim on onRevalidateSession.
  • expiresIn — optional anchor lifetime in seconds. If omitted, the session's own TTL governs. This is the real bound on the session unless you also implement onRevalidateSession.

A hook that throws, or answers a malformed shape, fails the grant.

onRevalidateSession

Called on refresh, to ask whether an extension-anchored session is still good.

input CilRevalidateInput {
  externalId: String!
  anchorToken: String
}

type CilRevalidateResult {
  active: Boolean!
  anchorToken: String
  expiresIn: Int
}
  • externalId / anchorToken — the values your onResolveLogin returned, or a value rotated by a previous onRevalidateSession call.
  • active — required. false ends the session; the refresh fails.
  • anchorToken — optionally rotate the sealed anchor value.
  • expiresIn — optionally extend the anchor, in seconds.
If you don't export this hook, a session simply ends when the expiresIn from onResolveLogin lapses, and the shopper signs in again.

Failures and liveness

The Commerce Integration Layer does no per-request introspection for an extension-anchored session: it holds no external credential of its own to check. Liveness is entirely your extension's call, asked once per refresh through onRevalidateSession, never on routine API requests. This mirrors the IdP-anchored session model, except the check lands in your own hook instead of an external IdP call.