Search by

magenxcommerce / module-social-login-graph-ql

magenxcommerce

Headless social-login bridge: a secret-gated GraphQL mutation that verifies a provider-signed OpenID Connect ID token (Google, Apple) against the issuer's JWKS and exchanges it for a Magento customer token, find-or-creating the customer by the token's verified email.

Package info

github.com/magenxcommerce/module-social-login-graph-ql

Type:magento2-module

pkg:composer/magenxcommerce/module-social-login-graph-ql

Statistics

Installs: 0

Dependents: 1

Suggesters: 0

Stars: 1

Open Issues: 0

v2.0.4 2026-09-18 09:24 UTC

This package is auto-updated.

Last update: 2026-10-24 19:26:43 UTC


README

Built with AI assistance. This module is built with significant use of Claude Code, under active human review and guidance. It is open source and in active development. AI assistance does not lower the engineering bar: every change must meet the same correctness, security, documentation and maintainability requirements as any other contribution.

No warranty. We are not responsible for any incompatibility. By installing this software, you accept responsibility for its security, upgrades, patches and monitoring.

A minimal, headless-friendly social-login bridge for Magento.

Stock Magento GraphQL can only mint a customer token from an email and password (generateCustomerToken). Social login produces a verified identity, not a password, so there is no stock way to turn "this is a verified Google user" into a Magento session. This companion module fills exactly that gap with a single mutation — nothing more. (Same "extend with GraphQL beside the core module" pattern as Magenx_ProductAlertGraphQl.)

What it adds

mutation {
  socialLogin(input: { secret: "…", idToken: "eyJhbGciOi…" }) {
    token
    created
  }
}

The resolver:

  1. Verifies the shared secret (constant-time) — fails closed if none configured.
  2. Verifies the idToken against the issuer's own published signing keys: signature, iss, aud (plus azp for a multi-audience token), exp/iat with 60s of clock leeway, and email_verified. Google and Apple ship configured; more issuers are a di.xml entry.
  3. Finds the customer by the token's verified email on the request's website, or creates one (random password, name from the token's given_name/family_name). One call therefore covers both register and login.
  4. For an existing account, refuses to mint a token when the account is locked out or still awaiting email confirmation — the same guard rails password sign-in applies. Accounts this mutation creates itself skip the confirmation check, since the provider has already verified the address.
  5. Returns a Magento customer access token (via the same Integration token model generateCustomerToken uses internally).

The optional firstname / lastname inputs are a display-name fallback only, used when creating an account whose token carries no name claims — Apple puts the name in the first authorization callback rather than in the ID token. They never influence which account is matched, so they carry no authority.

Security model

Two independent checks guard this mutation, and they answer different questions.

The shared secret answers "is this my broker calling?" Magento's /graphql may be reachable outside the storefront's Next.js proxy — directly (if nginx exposes a public /graphql location) or from anything on the internal network. The storefront's persisted-query allowlist is not a boundary here: it only guards the /api/graphql proxy, and this call deliberately bypasses it. Without the secret, anyone who can reach the endpoint could create customer records at will. The resolver fails closed when no secret is configured and compares with hash_equals.

The ID token answers "does this person own this email?" Magento verifies it itself, against keys fetched from the provider, so the broker is not trusted to assert an identity — it only relays what the provider signed. This is what keeps a leaked secret from being account takeover: the holder still cannot produce a Google or Apple signature over an email they do not control. The email is read from the verified claims and nowhere else.

Neither check replaces the other, so keep both:

Shared secret ID token verification
Answers Is this my broker? Does this person own this email?
If it alone is defeated Attacker can still only sign in as themselves Attacker cannot reach the mutation at all

Still the caller's job, because Magento cannot see it: completing the OAuth round-trip, checking the state parameter, and binding the nonce it sent to the nonce claim in the token it got back.

The secret must only ever live on the broker's server — never expose it or this mutation to the browser. A secret in a client bundle is a public constant.

Rejected calls (wrong secret, unverified or expired token, wrong audience) are logged as warnings with the store code and the reason, and with no PII, token material or secret material. Every rejection answers the caller with the same Not authorized., so the mutation cannot be used as an oracle; var/log/ is where the actual reason is.

Configuration

Set the shared secret and each provider's OAuth client id. Either as store config — Stores > Configuration > Magenx > Social Login, or the same three paths from the CLI:

bin/magento config:set magenx_social_login/general/shared_secret <secret>
bin/magento config:set magenx_social_login/general/google_client_id <client-id>.apps.googleusercontent.com
bin/magento config:set magenx_social_login/general/apple_client_id com.example.storefront

The secret field is encrypted on save, so a value entered in the admin is stored as ciphertext; one written straight to core_config_data as plain text is still read correctly, so an existing row keeps working.

Or as environment variables, which take precedence and keep the values out of the database:

MAGENX_SOCIAL_LOGIN_SECRET=<secret>
MAGENX_SOCIAL_LOGIN_GOOGLE_CLIENT_ID=<client-id>.apps.googleusercontent.com
MAGENX_SOCIAL_LOGIN_APPLE_CLIENT_ID=com.example.storefront

Each value is read from getenv(), $_ENV and $_SERVER, so any of the ways a Magento host passes environment through works — including an nginx fastcgi_param, which is what to use when the php-fpm pool keeps the default clear_env = yes and strips the worker environment:

fastcgi_param MAGENX_SOCIAL_LOGIN_GOOGLE_CLIENT_ID "<client-id>.apps.googleusercontent.com";

The client id must be the same one the broker used to obtain the token, or the aud check rejects it. A comma-separated list is accepted, for a store that serves sibling clients (web, iOS, Android) from one provider. An issuer with no client id configured rejects every token — including one that is otherwise perfectly valid — so both halves of a provider's setup must be present.

Store-config fallback

Each value also falls back to store config, read in store scope so a multi-site instance can hold a different value per store view. The module ships no system.xml yet, so these paths are not in the admin and bin/magento config:set rejects them ("The path doesn't exist") — the only way to populate them today is SQL:

INSERT INTO core_config_data (scope, scope_id, path, value)
VALUES ('default', 0, 'magenx_social_login/general/google_client_id',
        '<client-id>.apps.googleusercontent.com');

followed by bin/magento cache:clean config. An environment variable takes precedence over a row like this, so a stale variable will shadow it.

Outbound network access

Verification needs the issuer's JWKS, so the Magento container must be able to reach https://www.googleapis.com/oauth2/v3/certs and https://appleid.apple.com/auth/keys over HTTPS. The key set is cached for the lifetime the provider's Cache-Control header asks for (clamped to 5 min – 24 h), and an expired set is kept for a further 24 hours and served if the provider is unreachable — a transient outage degrades to slightly stale keys rather than to nobody being able to sign in. A token naming a key id that is not in the cached set triggers one forced refresh, so a key rotation costs a single extra round-trip rather than a wave of failures.

Adding another provider

Declare a virtual type in etc/di.xml with the provider's exact iss values and JWKS URL, add it to IssuerRegistry, and configure magenx_social_login/general/<code>_client_id. No PHP changes.

Install

bin/magento module:enable Magenx_SocialLoginGraphQl
bin/magento setup:upgrade