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

@maiguard-hq/id

v1.2.0

Published

The drop-in Continue with MaiGuard ID button and OIDC client for the browser

Readme

@maiguard-hq/id

"Continue with MaiGuard ID" as a drop-in button. One script tag, one call, a callback with the user.

<script src="https://cdn.maiguard.com/sdk/v1/maiguard-id.min.js"></script>
<div id="maiguard-btn"></div>
<script>
  MaiGuardID.init({ clientId: "mgid_…", redirectUri: location.origin + "/auth/callback" });
  MaiGuardID.renderButton("#maiguard-btn", {
    onSuccess: ({ code, verifier }) =>
      fetch("/auth/callback", { method: "POST", body: JSON.stringify({ code, verifier }) }),
  });
</script>

No build step, no dependencies, 3.6 KB gzipped. With a bundler:

npm install @maiguard-hq/id
import { init, renderButton } from "@maiguard-hq/id";

init({ clientId: "mgid_…", redirectUri: window.location.origin + "/auth/callback" });
renderButton("#maiguard-btn", { theme: "brand", size: "large", onSuccess });

Two modes

Popup, code to your backend (default). The consent screen posts a one-time code back to your page; you hand it and the PKCE verifier to your server, which exchanges them with the client secret and mints your own session. The browser never holds a durable credential.

Send both. The SDK always requests with a code_challenge, so the token endpoint always requires the matching code_verifier, confidential clients included. A code sent without it cannot be spent. @maiguard-hq/id-node is the other half.

PKCE in the browser (exchange: true). For an SPA with no backend. Legal because MaiGuard ID accepts public clients, but the tokens then live in that tab's memory and nowhere else. Never in localStorage.

Before it works

Add your site's origin to the app's Allowed origins in the MaiGuard ID developer console. It defaults to the origins of your redirect URIs, so it is usually already right. Popup sign-in from an origin that is not on the list is refused with ORIGIN_NOT_ALLOWED, and the error names the origins that would work.

Key the account on sub

sub identifies the person. email and name describe the selected profile: when someone signs in as a business, they are the business's support address and legal name, identical for every member of that business. Keying an account on the email merges colleagues into one another.

Use the email to display, and to offer a confirmed link to an existing account. Never a silent merge.

Options

| Option | Values | | --- | --- | | theme | brand (default), dark, light, outline | | size | small, medium, large | | text | continue_with, signin_with, signup_with | | shape | rounded, pill | | width | a number of pixels, or "full" | | mode | popup (default) or redirect | | carry | Your own JSON, returned by parseRedirect after a redirect | | exchange | true to exchange in the browser instead of returning a code |

scope may also be a function returning a promise, for apps whose requested fields are decided at click time. In redirect mode nothing is opened, so awaiting before navigating is safe; in popup mode the window is opened first and navigated after.

The button in redirect mode

renderButton("#maiguard-btn", {
  mode: "redirect",
  carry: { returnTo: "/dashboard" },
  scope: async () => fetchRequiredFields(),
});

Use it wherever a popup cannot post its result back, most notably when a native app claims your authorize URL as a universal link. Pair it with storage: "local" on init. There is no onSuccess in this mode: the page is leaving, and parseRedirect() on your callback route picks it up.

Redirect mode

signIn({ mode: "redirect" }) navigates instead of opening a popup, for embedded webviews and anywhere popups are unreliable. Call parseRedirect() on the way back.

init({ clientId: "mgid_…", redirectUri: location.origin + "/auth/callback" });

// Starting:
signIn({ mode: "redirect", carry: { returnTo: "/dashboard" } });

// On your callback page:
const { code, verifier, carry } = parseRedirect();
postToYourBackend({ code, verifier });
navigate(carry.returnTo);

Keeping your own data across the redirect

carry is any JSON you like. It is stored next to the verifier and handed straight back, which is how you remember where the user was going, or which of several buttons they pressed, without smuggling it through state. It never leaves the browser and is never sent to MaiGuard, so do not put anything there that your server has to trust.

If the code can come back in a different tab

The handoff lives in sessionStorage by default: per-tab, dies with the tab, narrowest blast radius.

Switch to localStorage when the callback can land in a different tab from the one that started:

init({
  clientId: "mgid_…",
  redirectUri: location.origin + "/auth/callback",
  storage: "local",        // default "session"
  storageTtlMs: 600000,    // default 10 minutes
});

The case this exists for is a native app claiming your authorize URL as a universal link. Tapping the button hands the flow to the app rather than navigating the tab, and the app returns the code by opening a new one. sessionStorage is invisible there, so the callback fails its state check every time on mobile while working perfectly on desktop, which makes it a miserable bug to chase.

The handoff is single-use and short-lived either way. It is cleared the moment parseRedirect reads it, and refused past storageTtlMs even if clearing was missed.

Asking for different scopes per sign-in

signIn({ scope }) overrides the scope given to init for that call, for apps whose requested fields are decided at sign-in time rather than at startup.

const scope = await fetchRequiredFields();   // e.g. "openid profile email doc:CAC"
signIn({ mode: "redirect", scope });

The button is inline-styled so it looks the same inside whatever CSS your page already has. Restyling it past these options defeats the point: a sign-in badge is a trust signal, and forty slightly different versions of it teach people that the badge means nothing.

API

| Call | Purpose | | --- | --- | | init({ clientId, redirectUri, issuer?, scope? }) | Configure. Fetches discovery once. | | renderButton(target, options) | Render the button and wire the ceremony | | signIn(options?) | Programmatic, for a custom trigger | | parseRedirect(href?) | Redirect mode: read and validate the callback URL | | exchange({ code, verifier }) | Exchange a code in the browser |

Ships TypeScript declarations, plus ESM, CJS, UMD and IIFE builds.

Errors

Every failure reaches onError as an error with a code.

| Code | Meaning | | --- | --- | | ORIGIN_NOT_ALLOWED | This page's origin is not on the app's allowlist | | popup_blocked | The browser refused the window, almost always because sign-in started outside a click | | popup_closed | The holder closed the window. Not worth shouting about; the button resets | | access_denied | The holder declined on the consent screen | | missing_state | Your half of the handoff is gone: expired, interrupted, or already used. Retrying works | | state_mismatch | The two halves disagree. Retrying will not fix it, and this is the one worth treating as suspicious | | token_exchange_failed | Usually a mismatched redirect URI, or a code already spent |

"The button does nothing" is nearly always a blocked popup. renderButton opens the window synchronously inside the click, so this only appears if you call signIn() yourself after an await.

Any backend language

@maiguard-hq/id-node is a convenience for one ecosystem, not a requirement. MaiGuard ID is a standard OpenID Connect provider, so the button above works with any server that can make an HTTP request. Point a conforming OIDC library at the issuer and it configures itself:

https://api.maiguard.com/id

league/oauth2-client (PHP), authlib (Python), omniauth_openid_connect (Ruby), coreos/go-oidc (Go), Spring Security (Java), Microsoft.AspNetCore.Authentication.OpenIdConnect (.NET) all work unchanged.

The server half is one POST and one signature check:

POST /v1/oauth/token
  grant_type=authorization_code&code=…&code_verifier=…
  &redirect_uri=…&client_id=…&client_secret=…

Configure client_secret_post. MaiGuard ID advertises client_secret_post and none, and does not accept client_secret_basic. Several libraries default to Basic auth and fail with an error naming none of this.

Two rules matter more than the plumbing, and no generic library enforces either: verify the id_token against the JWKS before trusting a claim, and key the account on sub, never on the email.

Related

Docs

https://docs.maiguard.com/maiguard-id-sdk

MIT