# Extension authentication

**Let your Project's own extension authenticate shoppers.**

A Project can delegate shopper authentication to its own extension instead of configuring an [identity provider](/commerce-integration-layer/development/identity-providers.md). 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.

```mermaid title="Extension authentication sign-in flow"
sequenceDiagram
	participant S as Shopper
	participant SF as Storefront
	participant IL as Commerce Integration Layer
	participant X as Your extension
	participant IS as Your identity system
	participant CT as commercetools

	S->>SF: Start sign-in
	SF->>IS: Authenticate (your own flow)
	IS-->>SF: Opaque code
	SF->>IL: POST /session (grant_type: extension)
	IL->>X: onResolveLogin(code)
	X-->>IL: externalId, profile fields
	IL->>CT: Create or update the matching Customer
	CT-->>IL: Customer
	IL-->>SF: Session token
```

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](/commerce-integration-layer/hosts-and-authorization.md#sessiongranttype).
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](/commerce-integration-layer/hosts-and-authorization.md#sessiongranttype).

Export two hooks from your bundle's `hooks` object, both optional:

```ts
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`.

```graphql
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.

```graphql
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](/commerce-integration-layer/development/identity-providers.md#idp-failures-and-latency), except the check lands in your own hook instead of an external IdP call.

## Related pages

- [Area overview page with navigation](/commerce-integration-layer.md)
- [Previous page: Identity providers](/commerce-integration-layer/development/identity-providers.md)
- [Search documentation and API specs](/search.md)