npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

oauth-redirect-relay

v0.2.1

Published

Signed-state OAuth broker pattern: share one registered redirect URI across many dev boxes, safely.

Downloads

50

Readme

oauth-redirect-relay

Share one registered OAuth redirect URI across many dev boxes — safely.

OAuth providers match redirect URIs by exact string, and some (Slack) reject http://localhost and forbid wildcards. Instead of registering every dev machine, register one HTTPS broker URL, sign the real dev-box target into the OAuth state, and let a tiny broker verify it and bounce the code back to the right machine.

This library gives you the signed-state codec, the broker verification (with an origin allowlist that prevents open redirects), and a CSRF nonce. It does not do PKCE or the token exchange — that stays in your app's OAuth client.

Install

bun add oauth-redirect-relay

Flow

The easiest path: hand it the authorize URL your OAuth client already builds, and the relay rewrites the redirect_uri to the broker and signs the state for you.

import { createRelay } from "oauth-redirect-relay";

const relay = createRelay({
  signingKey: process.env.RELAY_SIGNING_KEY!,
  brokerUrl: "https://broker.example.com/oauth-relay/callback",
});

// 1. dev box: wrap the provider authorize URL, then redirect the user to it
const { url, nonce } = await relay.wrapAuthorizeUrl(googleAuthUrl);
// (optionally) stash `nonce` in a short-lived cookie, then redirect to `url`

// 2. broker (the one registered HTTPS callback)
const result = await relay.handleCallback({ url: req.url });
// → { status: 302, location } | { status: 400, error, message }

// 3. dev box: finish the flow at your localhost callback
const { target, providerState, data } = await relay.verifyReturn({
  url: req.url,
  expectedNonce: storedNonce, // omit to enforce signature + expiry only
});
// `providerState` is the OAuth `state` your client originally set — verify it as usual,
// then do your own token exchange (use the broker URL as redirect_uri).

wrapAuthorizeUrl preserves the provider's original state (returned as providerState) and any other query params. For full control, the lower-level createState({ target, data, providerState }) builds a signed state directly.

Server-initiated flows (no per-browser nonce)

When the same server starts and finishes the flow and has nowhere to stash a per-browser nonce, call verifyReturn({ url }) without expectedNonce: the signature and expiry are still fully enforced; only the browser-binding check is skipped.

verifyReturn also accepts the state directly — verifyReturn({ state }) — for callbacks that have already parsed it off the request.

Options

| Option | Default | Meaning | |--------|---------|---------| | signingKey | — | HMAC-SHA256 secret (string) or CryptoKey, shared by dev box + broker | | brokerUrl | — | Broker callback URL; required for wrapAuthorizeUrl | | ttlSeconds | 600 | Signed lifetime of a state | | allowLoopback | true | Allow http://localhost / 127.0.0.1 on any port (mode A) | | allowedOrigins | [] | Extra exact origins allowed as targets |

Mode B (lock down, no loopback): allowLoopback: false + an explicit allowedOrigins list.

isRelayState(value) tells a relay token apart from a legacy state, e.g. to roll out the relay incrementally on a callback that may still receive un-wrapped states.

Hosting the broker

handleCallback is framework-agnostic — it takes a URL and returns { status, location }, so it drops into any server. See examples/express-broker.ts for a ~30-line Express handler you can copy:

app.get("/oauth-relay/callback", expressBroker(relay)); // from the example

Example broker

RELAY_SIGNING_KEY=your-secret bun examples/broker.ts

Deploy a broker

The broker is stateless — one route, one secret. Deploy it to AWS as a Lambda behind a public Function URL (the stable HTTPS endpoint you register as your redirect URI):

cd deploy/terraform
export TF_VAR_signing_key="$(openssl rand -hex 32)"
terraform init && terraform apply
terraform output broker_url

See deploy/terraform/README.md for inputs, outputs, and using it as a remote module.

Security notes

  • The allowlist is what stops this from being an open redirect — keep allowLoopback: false in any non-dev deployment and list exact origins.
  • The signing key is shared by both sides; use a dev-only key, never your prod app secret.
  • This library covers signed state + allowlist + nonce. Use PKCE in your OAuth client too.