diff --git a/.env.example b/.env.example
index 172185ca..6818fb8c 100644
--- a/.env.example
+++ b/.env.example
@@ -9,6 +9,8 @@ NUXT_SHOPIFY_CLIENTS_STOREFRONT_PRIVATE_ACCESS_TOKEN="YOUR_PRIVATE_ACCESS_TOKEN"
# Customer Account API
NUXT_SHOPIFY_CLIENTS_CUSTOMER_ACCOUNT_API_VERSION="2026-01"
NUXT_SHOPIFY_CLIENTS_CUSTOMER_ACCOUNT_CLIENT_ID="YOUR_APP_CLIENT_ID"
+NUXT_SHOPIFY_CLIENTS_CUSTOMER_ACCOUNT_CLIENT_SECRET="YOUR_APP_CLIENT_SECRET"
+NUXT_SHOPIFY_CLIENTS_CUSTOMER_ACCOUNT_SESSION_PASSWORD="A_RANDOM_32_CHARACTER_OR_LONGER_SECRET"
# Admin API
NUXT_SHOPIFY_CLIENTS_ADMIN_API_VERSION="2026-01"
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
deleted file mode 100644
index 9a895ab7..00000000
--- a/.git-blame-ignore-revs
+++ /dev/null
@@ -1,2 +0,0 @@
-# Eslint auto fix commit
-2d623e0e2bebaa788beb8e6d6959bc8d6df0798c
diff --git a/build.config.ts b/build.config.ts
index d62f1941..9447be9f 100644
--- a/build.config.ts
+++ b/build.config.ts
@@ -20,6 +20,5 @@ export default defineBuildConfig({
externals: [
'@shopify/hydrogen',
- 'nuxt-auth-utils',
],
})
diff --git a/bun.lock b/bun.lock
index 05736f4f..1140a963 100644
--- a/bun.lock
+++ b/bun.lock
@@ -43,7 +43,6 @@
"happy-dom": "^20.10.6",
"knip": "^6.17.1",
"nuxt": "^4.4.8",
- "nuxt-auth-utils": "^0.5.29",
"typescript": "^6.0.3",
"unbuild": "^3.6.1",
"vite": "^8.0.16",
@@ -52,11 +51,9 @@
},
"peerDependencies": {
"@shopify/hydrogen": "*",
- "nuxt-auth-utils": "*",
},
"optionalPeers": [
"@shopify/hydrogen",
- "nuxt-auth-utils",
],
},
"docs": {
@@ -89,7 +86,6 @@
},
"devDependencies": {
"@shopify/hydrogen": "^2026.4.4",
- "nuxt-auth-utils": "^0.5.29",
},
},
"playgrounds/playground-v4-mock": {
@@ -108,7 +104,6 @@
"@nuxtjs/shopify": "latest",
"@tailwindcss/typography": "^0.5.20",
"nuxt": "^4.4.8",
- "nuxt-auth-utils": "^0.5.29",
"zod": "^4.4.3",
},
"devDependencies": {
@@ -134,8 +129,6 @@
"@nuxtjs/shopify": "file:.",
},
"packages": {
- "@adonisjs/hash": ["@adonisjs/hash@9.1.1", "", { "dependencies": { "@phc/format": "^1.0.0", "@poppinss/utils": "^6.9.3" }, "peerDependencies": { "argon2": "^0.31.2 || ^0.41.0 || ^0.43.0", "bcrypt": "^5.1.1 || ^6.0.0" }, "optionalPeers": ["argon2", "bcrypt"] }, "sha512-ZkRguwjAp4skKvKDdRAfdJ2oqQ0N7p9l3sioyXO1E8o0WcsyDgEpsTQtuVNoIdMiw4sn4gJlmL3nyF4BcK1ZDQ=="],
-
"@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.131", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.29", "@vercel/oidc": "3.2.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CnjOZdywQaUnCyZ0N5wVNm7Sm63+NeHDVZQJKFX2IDq+t03SLwiiuoi3ILTLPlM+YSOhkQ/pvIDoR4qa98Zp5A=="],
"@ai-sdk/mcp": ["@ai-sdk/mcp@1.0.51", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.29", "pkce-challenge": "^5.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-03JEdJtB+K/I7a85GWJjrB+ypYY++ZFDTp2YJ5Wd0e0raZdFpBLvvX9LJIxPF1LRbs7PMtx6TIS1YMP0Dece+w=="],
@@ -840,8 +833,6 @@
"@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.5.6", "", { "os": "win32", "cpu": "x64" }, "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw=="],
- "@phc/format": ["@phc/format@1.0.0", "", {}, "sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ=="],
-
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
@@ -852,12 +843,6 @@
"@poppinss/exception": ["@poppinss/exception@1.2.3", "", {}, "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw=="],
- "@poppinss/object-builder": ["@poppinss/object-builder@1.1.0", "", {}, "sha512-FOrOq52l7u8goR5yncX14+k+Ewi5djnrt1JwXeS/FvnwAPOiveFhiczCDuvXdssAwamtrV2hp5Rw9v+n2T7hQg=="],
-
- "@poppinss/string": ["@poppinss/string@1.7.2", "", { "dependencies": { "@types/pluralize": "^0.0.33", "case-anything": "^3.1.2", "pluralize": "^8.0.0", "slugify": "^1.6.9" } }, "sha512-A182GLDfi36iDCbhDrHB0xzrPM1fO3GHnhCDIdadf8C6eycgct4m7zusbLwEh6GPaj2Pz5BVos7XK16w7tZ7wQ=="],
-
- "@poppinss/utils": ["@poppinss/utils@6.10.1", "", { "dependencies": { "@poppinss/exception": "^1.2.1", "@poppinss/object-builder": "^1.1.0", "@poppinss/string": "^1.3.0", "flattie": "^1.1.1", "safe-stable-stringify": "^2.5.0", "secure-json-parse": "^4.0.0" } }, "sha512-da+MMyeXhBaKtxQiWPfy7+056wk3lVIhioJnXHXkJ2/OHDaZfFcyKHNl1R06sdYO8lIRXcXdoZ6LO2ARmkAREA=="],
-
"@quansync/fs": ["@quansync/fs@1.0.0", "", { "dependencies": { "quansync": "^1.0.0" } }, "sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ=="],
"@react-router/dev": ["@react-router/dev@7.16.0", "", { "dependencies": { "@babel/core": "^7.27.7", "@babel/generator": "^7.27.5", "@babel/parser": "^7.27.7", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/preset-typescript": "^7.27.1", "@babel/traverse": "^7.27.7", "@babel/types": "^7.27.7", "@react-router/node": "7.16.0", "@remix-run/node-fetch-server": "^0.13.0", "arg": "^5.0.1", "babel-dead-code-elimination": "^1.0.6", "chokidar": "^4.0.0", "dedent": "^1.5.3", "es-module-lexer": "^1.3.1", "exit-hook": "2.2.1", "isbot": "^5.1.11", "jsesc": "3.0.2", "lodash": "^4.17.21", "p-map": "^7.0.3", "pathe": "^1.1.2", "picocolors": "^1.1.1", "pkg-types": "^2.3.0", "prettier": "^3.6.2", "react-refresh": "^0.14.0", "semver": "^7.3.7", "tinyglobby": "^0.2.14", "valibot": "^1.2.0", "vite-node": "^3.2.2" }, "peerDependencies": { "@react-router/serve": "^7.16.0", "@vitejs/plugin-rsc": "~0.5.21", "react-router": "^7.16.0", "react-server-dom-webpack": "^19.2.3", "typescript": "^5.1.0 || ^6.0.0", "vite": "^5.1.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", "wrangler": "^3.28.2 || ^4.0.0" }, "optionalPeers": ["@react-router/serve", "@vitejs/plugin-rsc", "react-server-dom-webpack", "typescript", "wrangler"], "bin": { "react-router": "bin.js" } }, "sha512-E/uNYnHbo+wepw+FudGwuN6au6y4dOfVuRRANYdCp5T+tLvGU/09s2uGzNQsxqushyae9wvWTLY0qMvvgowIyg=="],
@@ -1190,8 +1175,6 @@
"@types/parse-path": ["@types/parse-path@7.1.0", "", { "dependencies": { "parse-path": "*" } }, "sha512-EULJ8LApcVEPbrfND0cRQqutIOdiIgJ1Mgrhpy755r14xMohPTEpkV/k28SJvuOs9bHRFW8x+KeDAEPiGQPB9Q=="],
- "@types/pluralize": ["@types/pluralize@0.0.33", "", {}, "sha512-JOqsl+ZoCpP4e8TDke9W79FDcSgPAR0l6pixx2JHkhnRjvShyYiAYw2LVsnA7K08Y6DeOnaU6ujmENO4os/cYg=="],
-
"@types/resolve": ["@types/resolve@1.20.2", "", {}, "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="],
"@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
@@ -1510,8 +1493,6 @@
"capital-case": ["capital-case@1.0.4", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3", "upper-case-first": "^2.0.2" } }, "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A=="],
- "case-anything": ["case-anything@3.1.2", "", {}, "sha512-wljhAjDDIv/hM2FzgJnYQg90AWmZMNtESCjTeLH680qTzdo0nErlCxOmgzgX4ZsZAtIvqHyD87ES8QyriXB+BQ=="],
-
"ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
"chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="],
@@ -1922,8 +1903,6 @@
"flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="],
- "flattie": ["flattie@1.1.1", "", {}, "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ=="],
-
"fontaine": ["fontaine@0.8.0", "", { "dependencies": { "@capsizecss/unpack": "^4.0.0", "css-tree": "^3.1.0", "magic-regexp": "^0.10.0", "magic-string": "^0.30.21", "pathe": "^2.0.3", "ufo": "^1.6.1", "unplugin": "^2.3.10" } }, "sha512-eek1GbzOdWIj9FyQH/emqW1aEdfC3lYRCHepzwlFCm5T77fBSRSyNRKE6/antF1/B1M+SfJXVRQTY9GAr7lnDg=="],
"fontkitten": ["fontkitten@1.0.3", "", { "dependencies": { "tiny-inflate": "^1.0.3" } }, "sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw=="],
@@ -2546,8 +2525,6 @@
"nuxt": ["nuxt@4.4.8", "", { "dependencies": { "@dxup/nuxt": "^0.4.1", "@nuxt/cli": "^3.35.2", "@nuxt/devtools": "^3.2.4", "@nuxt/kit": "4.4.8", "@nuxt/nitro-server": "4.4.8", "@nuxt/schema": "4.4.8", "@nuxt/telemetry": "^2.8.0", "@nuxt/vite-builder": "4.4.8", "@unhead/vue": "^2.1.15", "@vue/shared": "^3.5.35", "chokidar": "^5.0.0", "compatx": "^0.2.0", "consola": "^3.4.2", "cookie-es": "^3.1.1", "defu": "^6.1.7", "devalue": "^5.8.1", "errx": "^0.1.0", "escape-string-regexp": "^5.0.0", "exsolve": "^1.0.8", "hookable": "^6.1.1", "ignore": "^7.0.5", "impound": "^1.1.5", "jiti": "^2.7.0", "klona": "^2.0.6", "knitwork": "^1.3.0", "magic-string": "^0.30.21", "mlly": "^1.8.2", "nanotar": "^0.3.0", "nypm": "^0.6.6", "ofetch": "^1.5.1", "ohash": "^2.0.11", "on-change": "^6.0.2", "oxc-minify": "^0.133.0", "oxc-parser": "^0.133.0", "oxc-transform": "^0.133.0", "oxc-walker": "^1.0.0", "pathe": "^2.0.3", "perfect-debounce": "^2.1.0", "picomatch": "^4.0.4", "pkg-types": "^2.3.1", "rou3": "^0.8.1", "scule": "^1.3.0", "semver": "^7.8.1", "std-env": "^4.1.0", "tinyglobby": "^0.2.17", "ufo": "^1.6.4", "ultrahtml": "^1.6.0", "uncrypto": "^0.1.3", "unctx": "^2.5.0", "unhead": "^2.1.15", "unimport": "^6.3.0", "unplugin": "^3.0.0", "unrouting": "^0.1.7", "untyped": "^2.0.0", "vue": "^3.5.35", "vue-router": "^5.1.0" }, "peerDependencies": { "@parcel/watcher": "^2.1.0", "@types/node": ">=18.12.0" }, "optionalPeers": ["@parcel/watcher", "@types/node"], "bin": { "nuxi": "bin/nuxt.mjs", "nuxt": "bin/nuxt.mjs" } }, "sha512-r/DGE4cNkEDclOw9tbJ18zqu+ix3me+7QCfumPdl5lBXGWgCajskzuq/HzDkHKfIZsn7ACVEjMLRNA2teh++bQ=="],
- "nuxt-auth-utils": ["nuxt-auth-utils@0.5.29", "", { "dependencies": { "@adonisjs/hash": "^9.1.1", "@nuxt/kit": "^4.3.1", "defu": "^6.1.4", "h3": "^1.15.4", "hookable": "^6.0.1", "jose": "^6.1.3", "ofetch": "^1.5.1", "openid-client": "^6.8.2", "pathe": "^2.0.3", "scule": "^1.3.0", "uncrypto": "^0.1.3" }, "peerDependencies": { "@atproto/api": "^0.13.15", "@atproto/oauth-client-node": "^0.2.0", "@simplewebauthn/browser": "^11.0.0", "@simplewebauthn/server": "^11.0.0" }, "optionalPeers": ["@atproto/api", "@atproto/oauth-client-node", "@simplewebauthn/browser", "@simplewebauthn/server"] }, "sha512-aQ9oD8QR51jUCe2BFEsO/G/E0K+XUy8Skjn3hYcNLiqZ9XE4Y3uT/ozBCmAQ+TR3WW6prj8vUR0wQes8W1N0PA=="],
-
"nuxt-component-meta": ["nuxt-component-meta@0.17.2", "", { "dependencies": { "@nuxt/kit": "^4.2.2", "citty": "^0.1.6", "mlly": "^1.8.0", "ohash": "^2.0.11", "scule": "^1.3.0", "typescript": "^5.9.3", "ufo": "^1.6.2", "vue-component-meta": "^3.2.2" }, "bin": { "nuxt-component-meta": "bin/nuxt-component-meta.mjs" } }, "sha512-2/mSSqutOX8t+r8cAX1yUYwAPBqicPO5Rfum3XaHVszxKCF4tXEXBiPGfJY9Zn69x/CIeOdw+aM9wmHzQ5Q+lA=="],
"nuxt-define": ["nuxt-define@1.0.0", "", {}, "sha512-CYZ2WjU+KCyCDVzjYUM4eEpMF0rkPmkpiFrybTqqQCRpUbPt2h3snswWIpFPXTi+osRCY6Og0W/XLAQgDL4FfQ=="],
@@ -2570,8 +2547,6 @@
"nypm": ["nypm@0.6.7", "", { "dependencies": { "citty": "^0.2.2", "pathe": "^2.0.3", "tinyexec": "^1.2.4" }, "bin": { "nypm": "./dist/cli.mjs" } }, "sha512-s3ds97SD5pd1dULE+tHUk1DrV0cSHOnsfpcdGATJ8JpBo21DoKqN9exTH4/2nhPQNOLomBdTFMicN94S4DrZrQ=="],
- "oauth4webapi": ["oauth4webapi@3.8.6", "", {}, "sha512-iwemM91xz8nryHti2yTmg5fhyEMVOkOXwHNqbvcATjyajb5oQxCQzrNOA6uElRHuMhQQTKUyFKV9y/CNyg25BQ=="],
-
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
"object-deep-merge": ["object-deep-merge@2.0.1", "", {}, "sha512-aKttDKcU3pyZqKcCkDhsMn70WmZFG2JGDQLP9EcLyTSIFQRCPWLAmBZRLJnrVUrhPG1jETEEbfdgbNtJf1LyMg=="],
@@ -2598,8 +2573,6 @@
"open": ["open@11.0.0", "", { "dependencies": { "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.1.0", "wsl-utils": "^0.3.0" } }, "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw=="],
- "openid-client": ["openid-client@6.8.4", "", { "dependencies": { "jose": "^6.2.2", "oauth4webapi": "^3.8.5" } }, "sha512-QSw0BA08piujetEwfZsHoTrDpMEha7GDZDicQqVwX4u0ChCjefvjDB++TZ8BTg76UpwhzIQgdvvfgfl3HpCSAw=="],
-
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
"orderedmap": ["orderedmap@2.1.1", "", {}, "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g=="],
@@ -2940,8 +2913,6 @@
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
- "safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="],
-
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
"sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="],
@@ -2952,8 +2923,6 @@
"scule": ["scule@1.3.0", "", {}, "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g=="],
- "secure-json-parse": ["secure-json-parse@4.1.0", "", {}, "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA=="],
-
"semver": ["semver@7.8.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA=="],
"send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="],
diff --git a/docs/content/1.getting-started/4.usage.md b/docs/content/1.getting-started/4.usage.md
index b6dabdcb..dae8c947 100644
--- a/docs/content/1.getting-started/4.usage.md
+++ b/docs/content/1.getting-started/4.usage.md
@@ -167,7 +167,7 @@ export default defineNuxtConfig({
```
::note
-Confidential client usage with the `clientSecret` is currently not supported for the Customer Account API.
+Both public clients and confidential clients (by also providing a `clientSecret`) are supported for the Customer Account API.
See the [Customer Account API guide](/essentials/customer-account) for more details on how to configure and use the Customer Account API client.
::
diff --git a/docs/content/2.essentials/2.configuration.md b/docs/content/2.essentials/2.configuration.md
index 1dc28828..f8d2015b 100644
--- a/docs/content/2.essentials/2.configuration.md
+++ b/docs/content/2.essentials/2.configuration.md
@@ -230,6 +230,10 @@ Configure one or more API clients:
Client ID for customer account API requests
:::
+ :::field{name="clientSecret" type="string"}
+ Client secret for confidential clients. When set, the OAuth token exchange is authenticated with the secret instead of PKCE - server-side only, optional
+ :::
+
:::field{name="scope" type="array"}
OAuth scopes to request during authentication - default: `['openid', 'email', 'customer-account-api:full']`
:::
@@ -242,10 +246,34 @@ Configure one or more API clients:
Logout URL for customer account API authentication - default: `/_auth/customer-account/logout`
:::
+ :::field{name="sessionURL" type="string"}
+ Endpoint that exposes the current session to the client - default: `/_auth/customer-account/session`
+ :::
+
:::field{name="redirectURL" type="string"}
Redirect URL to navigate to after successful customer account API authentication - default: `/`
:::
+ :::field{name="logoutRedirectURL" type="string"}
+ Redirect URL to navigate to after logout. Must be a registered logout URI in your Shopify app - optional
+ :::
+
+ :::field{name="session.password" type="string"}
+ Password used to encrypt the session cookie (at least 32 characters). Auto-generated in development, required in production - server-side only
+ :::
+
+ :::field{name="session.name" type="string"}
+ Name of the session cookie - default: `shopify-customer-account`
+ :::
+
+ :::field{name="session.maxAge" type="number"}
+ Lifetime of the session cookie in seconds - default: `604800` (7 days)
+ :::
+
+ :::field{name="tokenStorage" type="string | object | boolean"}
+ [unstorage](https://unstorage.unjs.io) mount or driver configuration for the server-side token store. Use a persistent driver (e.g. Redis) in production - default: in-memory
+ :::
+
:::field{name="dev.tunnelURL" type="string"}
Tunnel URL for local development with ngrok or similar - optional
:::
diff --git a/docs/content/2.essentials/4.customer-account.md b/docs/content/2.essentials/4.customer-account.md
index f79f4202..a9d2f77f 100644
--- a/docs/content/2.essentials/4.customer-account.md
+++ b/docs/content/2.essentials/4.customer-account.md
@@ -9,11 +9,13 @@ seo:
description: Using the Shopify Customer Account API in Nuxt
---
-The Customer Account API is a public API that allows you to access your Shopify store's customer data,
+The Customer Account API allows you to access your Shopify store's customer data,
such as customer profiles, addresses, and order history.
-It is designed to be used in client-side applications, such as a Nuxt frontend.
To use the Customer Account API, you need to obtain a client id.
-Confidential client authentication is currently not supported, so a client secret is not required.
+
+The module supports both **public clients** (which authenticate using PKCE) and **confidential clients**.
+To use a confidential client, provide a `clientSecret` in addition to the `clientId`. The secret is only ever
+used on the server and is never exposed to the browser.
::note
See the [module configuration](/essentials/configuration) to see how to set up the module for the Customer Account API.
@@ -29,13 +31,17 @@ you are using (e.g. `@shopify/hydrogen@2026.4.0` for API version 2026-04).
npm install @shopify/hydrogen
```
-For the customer account authentication flow, also make sure to install the `nuxt-auth-utils` package:
+The customer account session is split between an encrypted identity cookie and a server-side token store.
+In development, a session password is generated automatically and written to your `.env` file. In production,
+you must provide your own password (at least 32 characters) via the
+`NUXT_SHOPIFY_CLIENTS_CUSTOMER_ACCOUNT_SESSION_PASSWORD` environment variable or the `session.password` config option.
-```bash
-npm install nuxt-auth-utils
-```
+The access and refresh tokens never leave the server — they are kept in an [unstorage](https://unstorage.unjs.io)
+mount (the `tokenStorage` option), which defaults to in-memory. For production, configure a persistent driver
+(e.g. Redis) so sessions survive restarts and are shared across instances; otherwise customers will need to log in
+again whenever the server restarts.
-Then, once configured, you can use the Customer Account API in your Nuxt application via the `useCustomerAccount`
+Once configured, you can use the Customer Account API in your Nuxt application via the `useCustomerAccount`
and `useCustomerAccountData` composables. The `useCustomerAccount` composable is available to both server and client-side.
## Authentication
@@ -47,62 +53,44 @@ This means that the authentication flow will not work when running the app in de
To work around this, you can use a tunneling service like [ngrok](https://ngrok.com/) to create a public URL for your local development server.
See also the [Dev Bridge / Tunneling](#dev-bridge-tunneling) section below for a more seamless solution to this problem.
-### Logging in
+### The session composable
-To initiate the login flow, redirect the user to `/_auth/customer-account/callback`:
+The module provides a `useCustomerAccountSession` composable to manage authentication across both server and client.
+It exposes the current `user`, a reactive `isLoggedIn` flag, and `login` / `logout` actions:
```vue [~/app/pages/account.vue]
-
+
+
Logged in as {{ user?.email }}
+
+
+
+
+
```
+Calling `login()` starts the OAuth flow, and `logout()` ends both the local session and the Shopify session.
+
::tip
You can change the default authentication routes in the module configuration if you want to use different URLs for the login and logout routes.
::
-### Logging out
-
-To log the user out, redirect them to `/_auth/customer-account/logout`:
+### On the server
-```vue [~/app/pages/account.vue]
-
+Inside server routes and utilities, use the auto-imported `getCustomerAccountSession` and `requireCustomerAccountSession`
+helpers to read the session. `requireCustomerAccountSession` throws a `401` error when there is no authenticated customer:
-
-
-
-```
-
-### Checking login status
-
-The module uses [`nuxt-auth-utils`](https://github.com/atinux/nuxt-auth-utils) under the hood to manage the session.
-You can use its composables to check whether a user is currently authenticated:
-
-```vue [~/app/pages/account.vue]
-
+```ts [~/server/api/me.ts]
+export default defineEventHandler(async (event) => {
+ const { user } = await requireCustomerAccountSession(event)
-
-
-
Logged in as {{ user?.email }}
-
-
-
-
-
+ return user
+})
```
Once the user is authenticated, you can use the `useCustomerAccount` and `useCustomerAccountData` composables
diff --git a/docs/content/4.going-further/1.hooks.md b/docs/content/4.going-further/1.hooks.md
index a5cd33cf..e1033758 100644
--- a/docs/content/4.going-further/1.hooks.md
+++ b/docs/content/4.going-further/1.hooks.md
@@ -149,6 +149,66 @@ export default defineNitroPlugin((app) => {
```
::
+### Customer account auth hooks
+
+The customer account authentication flow runs entirely on the server, so these hooks are only available in a Nitro plugin.
+Use them to customize the OAuth request, run side effects on login/logout, or observe token refreshes.
+
+```ts [~/server/plugins/customer-account-auth.ts]
+export default defineNitroPlugin((app) => {
+ app.hooks.hook('customer-account:auth:authorize', ({ params }) => {
+ // Mutate the authorization request, e.g. request a localized login screen
+ params.locale = 'en'
+ })
+
+ app.hooks.hook('customer-account:auth:success', ({ user, tokens }) => {
+ // React to a successful login (merge the guest cart, track analytics, ...)
+ console.log('Customer logged in:', user.email)
+ })
+
+ app.hooks.hook('customer-account:auth:refresh', ({ tokens }) => {
+ console.log('Access token refreshed, expires at:', tokens.expiresAt)
+ })
+
+ app.hooks.hook('customer-account:auth:logout', ({ user }) => {
+ console.log('Customer logged out:', user?.email)
+ })
+
+ app.hooks.hook('customer-account:auth:error', ({ error }) => {
+ console.error('Customer account OAuth failed:', error)
+ })
+})
+```
+
+### Admin auth hooks
+
+The admin API authenticates with a machine-to-machine OAuth flow (client credentials, with refresh token rotation), so these hooks are only available in a Nitro plugin.
+Use them to customize the token request, or to observe when an access token is issued, refreshed, or fails to issue.
+
+These hooks only fire when the module obtains a token on your behalf. They are skipped when a static `accessToken` is configured.
+
+```ts [~/server/plugins/admin-auth.ts]
+export default defineNitroPlugin((app) => {
+ app.hooks.hook('admin:auth:request', ({ params }) => {
+ // Mutate the token request body before it is sent
+ params.scope = 'my-custom-scope'
+ })
+
+ app.hooks.hook('admin:auth:success', ({ token }) => {
+ // React to a freshly issued access token (client credentials grant)
+ console.log('Admin access token obtained, expires at:', token.expiresAt)
+ })
+
+ app.hooks.hook('admin:auth:refresh', ({ token }) => {
+ console.log('Admin access token refreshed, expires at:', token.expiresAt)
+ })
+
+ app.hooks.hook('admin:auth:error', ({ error }) => {
+ console.error('Admin token request failed:', error)
+ })
+})
+```
+
## Hooks reference
Hooks allows you to hook into the different stages of the module lifecycle.
@@ -185,20 +245,29 @@ Hooks allows you to hook into the different stages of the module lifecycle.
### Server Hooks (Runtime)
-| Hook | Arguments | Environments | Description |
-| ----------------------------------- | :--------------------------------: | :----------: | ------------------------------------------------------------- |
-| `storefront:client:configure` | `config` | Server | Called before the storefront client is created |
-| `storefront:client:create` | `client` | Server | Called after the storefront client is created |
-| `storefront:client:request` | `operation`, `options` | Server | Called before the storefront client sends a request |
-| `storefront:client:response` | `response`, `operation`, `options` | Server | Called after the storefront client receives a response |
-| `storefront:client:errors` | `errors` | Server | Called when a storefront client request contains errors |
-| `customer-account:client:configure` | `config` | Server | Called before the customer account client is created |
-| `customer-account:client:create` | `client` | Server | Called after the customer account client is created |
-| `customer-account:client:request` | `operation`, `options` | Server | Called before the customer account client sends a request |
-| `customer-account:client:response` | `response`, `operation`, `options` | Server | Called after the customer account client receives a response |
-| `customer-account:client:errors` | `errors` | Server | Called when a customer account client request contains errors |
-| `admin:client:configure` | `config` | Server | Called before the admin client is created |
-| `admin:client:create` | `client` | Server | Called after the admin client is created |
-| `admin:client:request` | `operation`, `options` | Server | Called before the admin client sends a request |
-| `admin:client:response` | `response`, `operation`, `options` | Server | Called after the admin client receives a response |
-| `admin:client:errors` | `errors` | Server | Called when an admin client request contains errors |
+| Hook | Arguments | Environments | Description |
+| ----------------------------------- | :--------------------------------: | :----------: | ------------------------------------------------------------------------------- |
+| `storefront:client:configure` | `config` | Server | Called before the storefront client is created |
+| `storefront:client:create` | `client` | Server | Called after the storefront client is created |
+| `storefront:client:request` | `operation`, `options` | Server | Called before the storefront client sends a request |
+| `storefront:client:response` | `response`, `operation`, `options` | Server | Called after the storefront client receives a response |
+| `storefront:client:errors` | `errors` | Server | Called when a storefront client request contains errors |
+| `customer-account:client:configure` | `config` | Server | Called before the customer account client is created |
+| `customer-account:client:create` | `client` | Server | Called after the customer account client is created |
+| `customer-account:client:request` | `operation`, `options` | Server | Called before the customer account client sends a request |
+| `customer-account:client:response` | `response`, `operation`, `options` | Server | Called after the customer account client receives a response |
+| `customer-account:client:errors` | `errors` | Server | Called when a customer account client request contains errors |
+| `customer-account:auth:authorize` | `params` | Server | Called before redirecting to Shopify to start the OAuth flow (mutable `params`) |
+| `customer-account:auth:success` | `user`, `tokens` | Server | Called after a successful login, before the session is persisted |
+| `customer-account:auth:refresh` | `tokens` | Server | Called after the customer account access token is refreshed |
+| `customer-account:auth:logout` | `user`, `idToken` | Server | Called before the session is cleared on logout |
+| `customer-account:auth:error` | `error` | Server | Called when the customer account OAuth flow fails |
+| `admin:auth:request` | `params` | Server | Called before the admin access token request is sent (mutable `params`) |
+| `admin:auth:success` | `token` | Server | Called after an admin access token is obtained (client credentials grant) |
+| `admin:auth:refresh` | `token` | Server | Called after the admin access token is refreshed (refresh token grant) |
+| `admin:auth:error` | `error` | Server | Called when the admin access token request fails |
+| `admin:client:configure` | `config` | Server | Called before the admin client is created |
+| `admin:client:create` | `client` | Server | Called after the admin client is created |
+| `admin:client:request` | `operation`, `options` | Server | Called before the admin client sends a request |
+| `admin:client:response` | `response`, `operation`, `options` | Server | Called after the admin client receives a response |
+| `admin:client:errors` | `errors` | Server | Called when an admin client request contains errors |
diff --git a/docs/content/index.md b/docs/content/index.md
index fa3406aa..9dfe8e43 100644
--- a/docs/content/index.md
+++ b/docs/content/index.md
@@ -133,10 +133,10 @@ CustomerAccount.vue
:::code-tree{default-value="app/components/CustomerAccount.vue"}
```vue [app/components/CustomerAccount.vue]