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.
- 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.
- Your storefront sends the resulting opaque value to
POST /sessionwithgrant_type: "extension", as described in Session grants. - The Commerce Integration Layer calls your extension's
onResolveLoginhook with that value. - Your extension verifies the shopper's credential however it needs to, and answers with an external identity.
- The Commerce Integration Layer maps the identity to a commercetools Customer, creating or updating one keyed on
externalId. - 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,
};
| Hook | Required for | Absence means |
|---|---|---|
onResolveLogin | The extension grant to work at all | The grant is refused — there's no fallback authentication path. |
onRevalidateSession | Refreshing an extension-anchored session | The 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'sexternalIdand 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 ononRevalidateSession.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 implementonRevalidateSession.
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 youronResolveLoginreturned, or a value rotated by a previousonRevalidateSessioncall.active— required.falseends 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.