| Server IP : 65.108.144.40 / Your IP : 216.73.217.165 Web Server : Apache/2.4.52 (Ubuntu) System : Linux ubuntu-8gb-hel1-1 5.15.0-173-generic #183-Ubuntu SMP Fri Mar 6 13:29:34 UTC 2026 x86_64 User : dev ( 1000) PHP Version : 8.2.30 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : ON Directory : /home/dev/webapps/fivewishes-bridge/ |
Upload File : |
# Keycloak → Shopify Multipass (D2C + B2B)
NestJS server that implements:
- D2C: Keycloak OIDC login → Shopify customer upsert → Multipass redirect
- B2B (optional): if `ENABLE_B2B=true` and Keycloak provides company claims, link the user into Shopify B2B Companies and assign roles
## Endpoints
- `GET /health` → `{ ok: true }`
- `GET /auth/login?return_to=/account` → redirects to Keycloak
- `GET /auth/forgot-password?return_to=/account` → redirects to Keycloak reset-password screen, then returns to `/auth/login`
- `GET /auth/callback?code=...&state=...` → validates Keycloak token, upserts customer, redirects to Shopify Multipass
## Required env
Copy `.env.example` to `.env` and fill values.
Key env vars:
- `BASE_URL` must be the public URL of this server (used for redirect URIs)
- `SHOPIFY_MULTIPASS_SECRET` comes from Shopify Plus Multipass settings
- `SHOPIFY_API_KEY` / `SHOPIFY_API_SECRET` come from your Partner (public) app
- `SHOPIFY_SCOPES` must include customers read/write (and B2B requires companies read/write)
- `TOKEN_ENCRYPTION_KEY` is used to encrypt per-shop access tokens at rest
- `KEYCLOAK_ISSUER` is your realm issuer URL
## Keycloak claims expected
Minimum (D2C):
- `email`
- `sub` (used as `external_id`)
- `given_name` / `family_name` (optional)
Optional (B2B):
- `company_id` (either `gid://shopify/Company/...` or your company external id)
- `company_location_id` (optional: `gid://shopify/CompanyLocation/...`)
- roles in `realm_access.roles` or `resource_access[client_id].roles`
Role mapping implemented:
- `b2b_admin` → `Company Admin`
- `b2b_buyer` → `Buyer`
- `finance` → `Buyer`
## Run locally
```zsh
npm i
npm run start:dev
```
## Install OAuth token (required once per shop)
This app stores the per-shop Admin API token encrypted on disk (default: `.local/shopify-tokens.enc.json`).
1) Start the server
2) Open in browser:
```text
http://localhost:3000/shopify/auth?shop=YOUR_SHOP.myshopify.com
```
3) Approve install in Shopify Admin → you should see a success message.
After that, `/auth/login` can upsert customers using the stored token.
## Shopify theme login link
Point your storefront login button to your server:
```liquid
<a href="https://YOUR_APP_DOMAIN/auth/login?return_to={{ request.path | url_encode }}">
Log in
</a>
```
## Option A (JIT) + Reset Password
Recommended for large existing customer bases: customers activate their Keycloak account on first login.
Storefront reset password link (sends the customer to Keycloak “Forgot password” / reset credentials):
```liquid
<a href="https://YOUR_APP_DOMAIN/auth/forgot-password?return_to={{ request.path | url_encode }}">
Forgot password
</a>
```
Keycloak client note (dev): allow redirect URIs for both `/auth/callback` and `/auth/login` (or `http://localhost:3000/*`).
## Notes
- State/nonce is signed and expires in 10 minutes.
- Customer external id is persisted as a metafield: namespace `keycloak`, key `external_id`.
## B2B flow (existing + new)
When `ENABLE_B2B=true` and Keycloak provides `company_id`, the callback will:
- Ensure a Shopify Customer exists (D2C upsert step)
- Link that Customer to the Shopify Company using `companyAssignCustomerAsContact`
- Assign a Company Contact role at the Company Location
Existing B2B customers:
- If the customer/contact already exists, the code is idempotent: it will look up the existing Company Contact and continue.
New B2B customers:
- Customer is created/updated from Keycloak login, then linked into the Company on first login.