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

@awesome-lang-auth/react

v0.1.0

Published

React bindings for the awesome-*-auth family: a typed, backend-agnostic client for the awesome-node-auth wire protocol (cookie on web, bearer on React Native).

Downloads

128

Readme

@awesome-lang-auth/react

React bindings for the awesome-*-auth family: one provider, one hook, two gates, and a small typed client that speaks the awesome-node-auth wire protocol directly.

It works with any backend of the family, because they all serve the same protocol: awesome-node-auth, awesome-go-auth, awesome-lambda-auth, and the python, rust and dart ports. It depends on none of them, only on react.

  • Cookie on the web, bearer on React Native. The transport is picked for you and can be overridden.
  • No global patching. The served auth.js replaces window.fetch. This package never does: app calls that need the session go through client.fetch.
  • React 18 and 19. State is read with useSyncExternalStore, so there are no tearing and no extra re-renders.
  • SSR-safe. Server renders see isLoading: true. The entry carries 'use client' for the Next.js App Router.
  • Router-agnostic gates. <ProtectedRoute> and <AnonymousOnly> render; you decide how to navigate.
npm install @awesome-lang-auth/react

Quick start

import { useState } from 'react';
import { AwesomeAuthProvider, ProtectedRoute, AnonymousOnly, useAwesomeAuth } from '@awesome-lang-auth/react';

// One provider at the root; each page picks its gate.
export function App() {
  return (
    <AwesomeAuthProvider options={{ apiPrefix: '/auth' }}>
      {location.pathname === '/login' ? (
        <AnonymousOnly redirectTo="/">
          <LoginForm />
        </AnonymousOnly>
      ) : (
        <ProtectedRoute fallback={<Spinner />} redirectTo="/login">
          <Dashboard />
        </ProtectedRoute>
      )}
    </AwesomeAuthProvider>
  );
}

function LoginForm() {
  const { login } = useAwesomeAuth();
  const [step, setStep] = useState<{ tempToken: string } | null>(null);

  async function onSubmit(email: string, password: string) {
    const r = await login(email, password);
    if (r.requires2fa) setStep({ tempToken: r.tempToken! }); // then client.validate2fa(tempToken, code)
    else if (!r.success) alert(r.error);
  }
  // ...
}

function Dashboard() {
  const { user, logout } = useAwesomeAuth();
  return <button onClick={logout}>Sign out {user?.email}</button>;
}

apiPrefix is where the backend mounts its auth router: /auth by default, or an absolute URL such as https://api.example.com/auth when the backend is on another origin.

The client

AwesomeAuthClient is framework-free. Create one yourself to share it with non-React code (an API layer, a store), then hand it to the provider:

import { AwesomeAuthClient } from '@awesome-lang-auth/react';

export const auth = new AwesomeAuthClient({ apiPrefix: 'https://api.example.com/auth' });

// Authenticated app calls: credentials attached, one refresh-and-retry on 401/403.
const res = await auth.fetch('https://api.example.com/orders');

auth.on('sessionExpired', () => router.navigate('/login'));
auth.onAuthStateChanged((user) => console.log('user is now', user));
<AwesomeAuthProvider client={auth}>...</AwesomeAuthProvider>

Every action method resolves to { success, error?, code? } (plus its payload) and never rejects on an HTTP error, like the served auth.js and ng-awesome-node-auth. Three are different: checkSession() resolves to the user or null, fetch() to a Response, and oauthUrl() returns a string.

| Area | Methods | | --- | --- | | Session | checkSession, refresh, logout, getActiveSessions, revokeSession, fetch, oauthUrl(provider) | | Login | login(email, password), register(email, password, firstName?, lastName?), updateProfile | | Password | forgotPassword, resetPassword(token, password), changePassword, setPassword | | Magic link | sendMagicLink, verifyMagicLink, send2faMagicLink(tempToken) | | SMS | sendSmsLogin, verifySmsLogin, send2faSms(tempToken), validateSms(tempToken, code), addPhone | | 2FA | setup2fa, verify2faSetup(code, secret), validate2fa(tempToken, code), disable2fa | | Email | resendVerificationEmail, verifyEmail, requestEmailChange, confirmEmailChange | | Linking | requestLinkingEmail, verifyLinkingToken, verifyConflictLinkingToken, getLinkedAccounts, unlinkAccount | | Account | deleteAccount |

Login with a second factor. When the account has 2FA, login resolves to { success: true, requires2fa: true, tempToken, availableMethods } and the user stays signed out. Complete it with validate2fa(tempToken, code) (TOTP), send2faSms + validateSms, or send2faMagicLink. When the account must enrol first, the backend answers 403 and login resolves to { success: false, requires2FASetup: true, tempToken }.

Argument order. resetPassword(token, password) follows the served auth.js. ng-awesome-node-auth has the order reversed.

The user is the GET /me payload. Its id is sub, not id. It also has email, role, loginProvider, isEmailVerified and isTotpEnabled, plus metadata, roles and permissions when the backend has those stores. Type your own claims with interface MyUser extends AuthUser { tenantId: string } and useAuthUser<MyUser>(). The type parameter is an assertion: nothing checks it against what the backend sends.

Transport: cookie or bearer

| | cookie (web default) | bearer (React Native default) | | --- | --- | --- | | Tokens | HttpOnly cookies set by the backend | JSON body, kept in a TokenStorage | | Requests | credentials: 'include' + X-CSRF-Token from the CSRF cookie | X-Auth-Strategy: bearer + Authorization: Bearer | | Refresh | POST /refresh (cookie) | POST /refresh with { refreshToken } |

Credentials, CSRF tokens and bearer tokens are attached only to requests for the backend's origin. A client.fetch to any other origin goes out untouched.

Override the default with mode:

new AwesomeAuthClient({ apiPrefix: 'https://api.example.com/auth', mode: 'bearer', storage });

Bearer tokens live in memory by default, so the session ends when the app does. To persist them on React Native, pass a storage. Sync and async methods both work:

import * as SecureStore from 'expo-secure-store';
import type { TokenStorage } from '@awesome-lang-auth/react';

const storage: TokenStorage = {
  load: async () => JSON.parse((await SecureStore.getItemAsync('auth')) ?? 'null'),
  save: (t) => SecureStore.setItemAsync('auth', JSON.stringify(t)),
  clear: () => SecureStore.deleteItemAsync('auth'),
};

On React Native, apiPrefix must be an absolute URL.

Known limits of the backends

  • Bearer mode in a browser, backend on another origin. X-Auth-Strategy is not in the CORS allow-list of awesome-node-auth or awesome-lambda-auth, so the preflight fails. Allow the header on the backend, or use cookie mode. React Native has no CORS and is unaffected.
  • Cookie mode, backend on another site. The CSRF cookie belongs to the backend host, so the page cannot read it and send it back. Serve both from one parent domain, or disable CSRF. A CSRF_INVALID answer is returned as-is, without a refresh.
  • Bearer logout on awesome-node-auth. The client sends the refresh token in the logout body, and awesome-go-auth revokes it. awesome-node-auth only revokes a session found through the access-token cookie, so a bearer refresh token stays valid on the server until it expires.
  • requestLinkingEmail in bearer mode on awesome-node-auth with CSRF on. That route checks CSRF even for bearer requests, and awesome-go-auth does not.

Refresh policy. A 401/403 from the backend triggers one shared refresh and one retry. There are three exceptions. SESSION_REVOKED signs the user out. Any other error code (such as CSRF_INVALID or INVALID_TEMP_TOKEN) is returned as-is. The auth answers themselves (/login, /refresh, /2fa/verify, /sms/verify, /magic-link/verify, ...) are never retried, so a wrong code is not submitted twice. A network failure never signs anyone out: before the first successful check the state stays isLoading, with error set. Call checkSession() to retry.

Gates

<ProtectedRoute
  fallback={<Spinner />}          // while loading, and while leaving
  redirectTo="/login"             // full-page navigation for signed-out visitors
  onUnauthenticated={() => nav('/login')} // or drive your router (wins over redirectTo)
  role={['admin', 'editor']}      // optional: user.role or user.roles
  forbidden={<p>No access</p>}    // signed in without the role
>
  <Admin />
</ProtectedRoute>

<AnonymousOnly fallback={null} onAuthenticated={() => nav('/')}>
  <LoginForm />
</AnonymousOnly>

React Router. Pass onUnauthenticated={() => navigate('/login', { replace: true })}, using navigate from useNavigate(). You can also branch on useAwesomeAuth().isAuthenticated and render <Navigate> yourself.

Next.js App Router. The package entry is marked 'use client', so you can render <AwesomeAuthProvider> from your root layout. Server Components can import hasRole, SERVER_SNAPSHOT and the types from @awesome-lang-auth/react/server, which has no client boundary. For server-side gating, check the session cookie in middleware. This package covers the client side.

SSR

On the server the provider renders with { user: null, isAuthenticated: false, isLoading: true }. That is also the state React hydrates against, so there is no mismatch. The session check starts in the browser after hydration. Nothing reads window or document at import time.

Not in this package (yet)

  • The admin console API and the AuthTools SSE stream.
  • An adapter for a page that already loads the served auth.js (window.AwesomeNodeAuth). Use one or the other: auth.js patches the global fetch.

Develop

Node is not required on the host. scripts/toolchain.sh runs node:22 in Docker, with node_modules in a named volume:

./scripts/toolchain.sh npm ci
./scripts/toolchain.sh npm test
./scripts/toolchain.sh npm run build
./scripts/toolchain.sh npm run lint:package

License

MIT