feat(shopify-auth): add Shopify embedded app and webhook authentication middleware - #2090
feat(shopify-auth): add Shopify embedded app and webhook authentication middleware#2090besart-k wants to merge 9 commits into
Conversation
…te scope implications
…y and secret retrieval
…ware tests - Added a constant for TOKEN_EXCHANGE in middleware tests for clarity. - Refactored session tests to use parameterized tests for better readability and maintainability. - Improved assertions in middleware tests to ensure proper rejection of invalid tokens and headers. - Enhanced memory storage tests to ensure proper handling of session data, including expiry fields. - Consolidated similar test cases for missing scopes into parameterized tests for better organization.
…GraphQL and storage operations
…y and concurrency handling
🦋 Changeset detectedLatest commit: 763da61 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
At first glance, honestly, I don't want to host this middleware with a large codebase in this repo. |
Totally fair concern, and I'd rather solve that than argue about it. Two bits of context that might reframe the size: The diff is ~2,550 lines, but only 1,071 of that is source. The rest is 1,136 lines of tests and a 339-line README. For comparison, oauth-providers is 2,757 LOC of source, mcp is 2,181, and zod-openapi is 1,598, so this would land mid-pack rather than at the top. Why I wrote this: I was building a Shopify app on Cloudflare and there was nothing to start from. Shopify's library has a cf-worker adapter, so the primitives work, but the packages that give you actual session-token middleware are Express and Remix only. On Hono you hand-roll it yourself, and the fiddly parts (App Bridge retry headers, token exchange) are easy to get subtly wrong. That said, I'm happy to go whichever way you prefer:
What do you think? |
Adds
@hono/shopify-auth, a zero-dependency middleware for authenticatingShopify embedded app requests and webhooks.
Everything is built on Web Crypto and
fetch, so it runs unchanged onCloudflare Workers, Deno, Bun, Node.js, Vercel, and Fastly. There is no
dependency on
@shopify/shopify-apiand no Node builtins.What it does
Three middlewares, each doing one thing:
shopifySessionToken— verifies the App Bridge session token (HS256 JWT)and establishes which shop is calling. No storage, no network calls, no state.
shopifyAccessToken— the above, plus guarantees a usable Admin APIoffline access token. Implements the token exchange grant Shopify
recommends for embedded apps, including expiring tokens, refresh-token
rotation, and recovery from tokens revoked out of band. No OAuth redirect, no
cookies.
shopifyWebhook— verifies the HMAC signature over the raw request body,and validates the unsigned
X-Shopify-Shop-Domainheader as amyshopify.comhost before it can reach an Admin API URL.Persistence is left to the app through a small
ShopifySessionStorageinterface, so the package ships no database opinion. A
memoryStorage()helperis included for tests and local development.
Why it might be worth having
Shopify's own libraries assume a Node runtime, which rules them out on Workers
and other edge platforms — the environments Hono is most often deployed to. The
modern token-exchange flow is also small enough to implement directly, which is
what makes a zero-dependency version practical.
Validation
Beyond the unit tests, this has been running in a production Shopify app
against a real store. First-install token exchange, the refresh grant with
rotation, token reuse from storage, D1-backed persistence, and HMAC
verification across seven webhook topics have all been exercised end to end.
Replacing that app's hand-rolled equivalent removed ~445 lines.
Two things came out of that integration and are already folded in: the storage
contract now documents that last-write-wins must hold atomically (a
delete-then-insert SQL adapter duplicates rows under concurrent cold-cache
requests), and the middlewares return a bare
MiddlewareHandlerin line withthe other auth packages here.
Notes for reviewers
refresh, storage, scopes, and the middlewares themselves.
attwreports no problems.publintnotes the absence ofengines.nodeandsideEffects; neither is set by the sibling packages I checked, so I havefollowed the existing convention — happy to add either.
clerk-auth, with test files andtest-utils.tsexcludedfrom the published graph.
The author should do the following, if applicable
pnpm changesetat the top of this repo and push the changeset