Skip to content

feat(shopify-auth): add Shopify embedded app and webhook authentication middleware - #2090

Open
besart-k wants to merge 9 commits into
honojs:mainfrom
besart-k:feat/shopify-auth
Open

feat(shopify-auth): add Shopify embedded app and webhook authentication middleware#2090
besart-k wants to merge 9 commits into
honojs:mainfrom
besart-k:feat/shopify-auth

Conversation

@besart-k

Copy link
Copy Markdown

Adds @hono/shopify-auth, a zero-dependency middleware for authenticating
Shopify embedded app requests and webhooks.

Everything is built on Web Crypto and fetch, so it runs unchanged on
Cloudflare Workers, Deno, Bun, Node.js, Vercel, and Fastly. There is no
dependency on @shopify/shopify-api and 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 API
    offline 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-Domain header as a
    myshopify.com host before it can reach an Admin API URL.

Persistence is left to the app through a small ShopifySessionStorage
interface, so the package ships no database opinion. A memoryStorage() helper
is 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 MiddlewareHandler in line with
the other auth packages here.

Notes for reviewers

  • 117 tests across session token verification, HMAC, token exchange and
    refresh, storage, scopes, and the middlewares themselves.
  • attw reports no problems. publint notes the absence of engines.node and
    sideEffects; neither is set by the sibling packages I checked, so I have
    followed the existing convention — happy to add either.
  • JSR config mirrors clerk-auth, with test files and test-utils.ts excluded
    from the published graph.
  • I am happy to maintain this package and respond to issues and PRs against it.

The author should do the following, if applicable

  • Add tests
  • Run tests
  • pnpm changeset at the top of this repo and push the changeset
  • Follow the contribution guide

…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.
@changeset-bot

changeset-bot Bot commented Aug 16, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 763da61

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@hono/shopify-auth Minor

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

@yusukebe

Copy link
Copy Markdown
Member

At first glance, honestly, I don't want to host this middleware with a large codebase in this repo.

@besart-k

Copy link
Copy Markdown
Author

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.
Zero runtime dependencies. hono is a peer dep, everything else is Web Crypto and fetch. No @shopify/shopify-api, no Node built-ins. Built ESM is ~22 KB.
The reason it isn't 200 lines is that Shopify embedded auth is genuinely three separate flows: session-token (JWT) verification, token exchange for Admin API access tokens, and webhook HMAC verification. Apps typically need all three.

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:

  1. Trim to the core. Ship only shopifySessionToken + shopifyWebhook (~450 LOC) and drop token exchange, the Admin GraphQL helper, and session storage. Smaller surface, smaller maintenance area.
  2. Keep it out of this repo. I publish it under my own scope and it can just gets listed in the Hono docs as third-party middleware. No burden on you at all.
  3. Keep it as-is with me on the hook. I will be using this in production and will stay responsive to issues, happy to be listed as the maintainer for this package so Shopify API changes route to me, not you.

What do you think?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants