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

shad-auth-react

v0.4.0

Published

«ورود با شاد» برای برنامه‌های React — Login with Shad, React client

Readme

shad-auth-react

«ورود با شاد» for React apps — the client half of the reverse SSO flow, where a user starts on your app and signs in with their Shad account instead of being sent out from Shad.

Zero runtime dependencies. React is a peer dependency.

Install

npm install shad-auth-react

React is never bundled and never resolved from inside this package — the host app's React instance is the only one used. Hooks break the moment a second copy exists, with Cannot read properties of null (reading 'useState').

A registry install cannot produce that copy: only dist/ is published. A local npm link or file: install can, because the linked folder is a full checkout and bundlers resolve react from the real path, not the symlink. Guard it:

  • this package ships an .npmrc with legacy-peer-deps=true, so npm install here does not pull a peer React into node_modules;
  • in the host app, pin resolution to one copy:
// next.config.mjs
webpack: config => {
  config.resolve.alias = {
    ...config.resolve.alias,
    react: path.resolve(rootDir, 'node_modules/react'),
    'react-dom': path.resolve(rootDir, 'node_modules/react-dom'),
  };
  return config;
},

App Router

ShadLogin carries the "use client" directive, so it can be imported straight into a server component. The imperative startShadLogin is browser-only — call it from a client component or an event handler.

Use

import { ShadLogin } from "shad-auth-react";

<ShadLogin
  clientId="shad-panel"
  redirectUri="https://panel.example.ir/auth/callback"
  onSuccess={(code) => api.post("/auth/shad", { code })}
  onError={(err) => toast(err.code)}
/>

That is the whole integration. The button opens the Shad login in a popup and hands you a single-use authorization code.

The code is not an identity token and cannot be read in the browser. Post it to your own backend, which exchanges it with Shad SSO for the token, and set your own session cookie from there.

id, err := shad.Exchange(ctx, code)
if err != nil { /* ... */ }

// From here on, role and access are your application's business.
session.Start(id.Subject)

Styling the button

The built-in look follows Google's "Sign in with Google" button: its published theme colours, its heights, radius and hover and press tints, with the Shad mark in place of the G.

<ShadLogin {...props} theme="dark" size="medium" shape="pill" />

| Prop | Values | Default | |---|---|---| | theme | light — white, grey outline · dark — near-black, for dark backgrounds · neutral — light grey, no outline | light | | size | large 40px · medium 32px · small 20px | large | | shape | rectangular · pill | rectangular |

Nothing follows the system colour scheme by itself — as with Google's button, the theme is yours to match to the background. An app with its own dark mode passes theme={isDark ? "dark" : "light"}.

style is merged over the built-in look, so small changes need nothing else:

<ShadLogin {...props} style={{ width: "100%", borderRadius: 8 }} />

A className alone cannot restyle it: the defaults are inline, and an inline style beats any class. Pass unstyled to drop them and let your own CSS decide everything:

<ShadLogin {...props} unstyled className="btn btn-primary w-full" />

The Shad mark keeps its own colours on every background, as Google's G does. While a login is running the button is disabled and carries aria-busy="true" — target that for a loading style, with [aria-busy="true"] in CSS or aria-busy:opacity-60 in Tailwind.

Native button attributes pass straight through — id, aria-label, data-testid, name and the rest:

<ShadLogin {...props} id="shad-login" data-testid="shad-login" />

onClick and type belong to the component, and onError is the failed-login callback, not the DOM event.

Imperative API

For a custom button, skip the component:

import { startShadLogin, ShadAuthError } from "shad-auth-react";

button.addEventListener("click", () => {
  startShadLogin({ clientId, redirectUri })
    .then(sendToBackend)
    .catch((err: ShadAuthError) => console.warn(err.code));
});

Call it directly inside the click handler. Browsers only allow window.open while a user gesture is being handled, so opening after an await is treated as unsolicited and blocked.

Options

| Option | Required | Meaning | |---|---|---| | clientId | yes | Issued when your app was registered with Shad SSO. | | redirectUri | yes | Must match a registered URL exactly — the registry accepts no wildcards, so a value assembled at runtime from window.location is usually rejected. | | stage | no | false (default) logs in against production, https://sso.noyanet.com. true uses staging, https://sso-stage.noyanet.com — set it in your non-production builds, since the package cannot tell them apart. | | ssoOrigin | no | Any other SSO origin. Wins over stage when set. | | acr | no | Minimum authentication level, e.g. "aal2" to force a second factor. Can raise the level above what the client is registered for, never lower it. | | onPopupBlocked | no | "redirect" (default) or "error". |

Popup blocked

By default the whole page navigates to Shad instead of opening a window, which is what strict popup blockers, Safari and in-app WebViews require. Your app does nothing extra — the code comes back to your registered redirectUri as a query parameter.

If that URL is a frontend route rather than a backend endpoint, read it with:

import { readRedirectResult } from "shad-auth-react";

const code = readRedirectResult(); // null when the URL carries no result

It verifies the state, strips the parameters from the address bar, and throws a ShadAuthError on mismatch.

Errors

onError always receives a ShadAuthError with a code:

| code | When | |---|---| | access_denied | The user cancelled or closed the login window. | | popup_blocked | Only with onPopupBlocked: "error". | | state_mismatch | The reply did not carry back the state we sent. Treat as hostile. | | server_error | Anything Shad reported without a dedicated code. |

What this package will not do

  • Store anything. No token, no code, no localStorage. A token kept in the frontend turns any XSS anywhere in your app into standing access.
  • Tell you who the user is. Identity comes from your backend's exchange.
  • Decide what the user may do. Shad SSO proves identity and nothing else; roles and permissions are entirely your application's business.
  • Ship CSS. Styling is inline so nothing collides with, or gets purged by, your own setup. See Styling the button.

Build

npm install
npm run build     # dist/esm + dist/cjs + types
npm run typecheck