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

@btcv/auth-provider

v0.2.0

Published

Plug-and-play OIDC/WebAuthn auth module for Next.js apps backed by me.btcv.fr

Readme

@btcv/auth-provider

Module d'authentification plug-and-play pour applications Next.js (App Router), façade au-dessus du serveur OIDC me.btcv.fr. Le module ne gère pas les flows de login/register : ils ont lieu sur le site d'auth externe. Il fournit :

  • un middleware Next.js qui protège les routes et injecte les headers user
  • un catch-all route handler (/api/auth/[...auth]) qui gère login → callback PKCE → session → refresh → logout → proxy authentifié vers ${authUrl}/api/*
  • un <AuthProvider> + hooks (useSession, useAccount, usePasskeys, useAuthGuard)
  • un <AccountDialog> prêt à l'emploi (profil, sécurité/passkeys, sessions, providers)
  • des helpers serveur (getSession, withAuth) pour RSC et route handlers

Installation

npm install @btcv/auth-provider

Peers requis dans l'app consommatrice :

  • next >= 14
  • react >= 18 / react-dom >= 18
  • @simplewebauthn/browser (uniquement si tu utilises les passkeys côté client)

Styles (Tailwind v4)

Le <AccountDialog> utilise des classes Tailwind utilitaires (md:h-140, w-[225px], etc.). Tailwind v4 ne scanne pas node_modules par défaut — sans import explicite, le dialog rend comme une mini-boîte sans dimensions.

Dans le CSS global de ton app :

@import "tailwindcss";
@import "@btcv/ui/styles.css";
@import "@btcv/auth-provider/styles.css";  /* classes du AccountDialog */

styles.css est un fichier pré-compilé qui contient uniquement les classes utilitaires utilisées par les composants du package. Les tokens (couleurs, radius, fonts) viennent de @btcv/ui/styles.css que tu importes déjà.

AccountDialog depuis un DropdownMenuItem (Radix)

Si tu ouvres le dialog depuis un <DropdownMenuItem>, un setTimeout(0) est nécessaire pour laisser le dropdown restaurer pointer-events sur <body> avant que le dialog pose son propre lock — sinon la page reste bloquée après fermeture :

const [accountOpen, setAccountOpen] = useState(false);

<DropdownMenuItem
  onSelect={() => setTimeout(() => setAccountOpen(true), 0)}
>
  Mon compte
</DropdownMenuItem>

<AccountDialog open={accountOpen} onClose={() => setAccountOpen(false)} />

Sans le setTimeout, le dropdown et le dialog se disputent la gestion du pointer-events: none sur le body (conflit Radix Dropdown ↔ Dialog).


Configuration minimale

1. Variables d'environnement

AUTH_URL=https://me.btcv.fr
AUTH_CLIENT_ID=your-client-id
AUTH_CLIENT_SECRET=your-client-secret   # optionnel (clients confidentiels)

2. Route handler catch-all

app/api/auth/[...auth]/route.ts :

import { createAuthHandlers } from "@btcv/auth-provider/next";

export const { GET, POST, PATCH, DELETE } = createAuthHandlers({
  authUrl: process.env.AUTH_URL!,
  clientId: process.env.AUTH_CLIENT_ID!,
  clientSecret: process.env.AUTH_CLIENT_SECRET,
  afterSignInUrl: "/dashboard",
  afterSignOutUrl: "/",
});

Routes exposées automatiquement :

| Route | Rôle | | --------------------------- | ---------------------------------------------------------- | | GET /api/auth/login | Démarre le flow OAuth (PKCE) — ?redirect_uri= supporté | | GET /api/auth/callback | Callback OAuth, échange le code, set les cookies | | GET /api/auth/session | Retourne { user } à partir des cookies | | POST /api/auth/refresh | Refresh silencieux du token | | POST /api/auth/logout | Détruit la session locale + remote | | * /api/auth/proxy/... | Proxy authentifié vers ${authUrl}/api/... (CSRF requis) |

3. Middleware

middleware.ts à la racine :

import { createAuthMiddleware } from "@btcv/auth-provider/next";

export default createAuthMiddleware({
  authUrl: process.env.AUTH_URL!,
  publicRoutes: ["/", "/about", "/pricing/*"],
});

export const config = {
  matcher: ["/((?!_next|favicon.ico|.*\\..*).*)"],
};

Comportement :

  • Les routes /api/auth/* passent toujours.
  • Si session valide → injecte x-user-id, x-user-email, x-user-name, x-user-image dans la requête (et strip ces headers s'ils viennent du client, anti-forgery).
  • Si access token expiré + refresh dispo → tente un refresh silencieux et forward les Set-Cookie.
  • Sinon → redirect vers /api/auth/login?redirect_uri=<pathname>.
  • publicRoutes accepte les wildcards en suffixe (/blog/*).

4. AuthProvider + AccountDialog (client)

"use client";
import { AuthProvider, AccountDialog } from "@btcv/auth-provider";

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <AuthProvider authUrl={process.env.NEXT_PUBLIC_AUTH_URL!}>
      {children}
    </AuthProvider>
  );
}

Note : <AuthProvider> n'a besoin que de authUrl. Toutes les requêtes API passent par basePath (défaut /api/auth, donc le proxy du route handler) — il n'y a pas de apiUrl séparé.

const [open, setOpen] = useState(false);
<AccountDialog open={open} onClose={() => setOpen(false)} defaultTab="profile" />

Hooks client

import { useSession, useAccount, usePasskeys, useAuthGuard } from "@btcv/auth-provider";

const { user, isLoading, isAuthenticated } = useSession();
const { updateProfile, revokeSession } = useAccount();
const { passkeys, addPasskey, removePasskey } = usePasskeys();
useAuthGuard(); // redirige automatiquement si non auth (utile en CSR pur)

Helpers serveur (RSC / route handlers)

import { getSession, withAuth } from "@btcv/auth-provider/server";

// dans un Server Component
const session = await getSession(); // SessionData | null

// wrapper pour les route handlers
export const GET = withAuth(async (req, { user }) => {
  return Response.json({ hello: user.email });
});

CSRF

Toute requête mutating (POST/PATCH/DELETE) à travers /api/auth/proxy/* doit inclure le header X-CSRF-Token égal à la valeur du cookie CSRF posé par /api/auth/session. Les hooks fournis (useAccount, usePasskeys, etc.) gèrent ça automatiquement — c'est seulement à savoir si tu appelles le proxy à la main.

Référence backend

Le contrat REST complet (endpoints existants vs. à créer côté me.btcv.fr, headers, payloads) vit dans api-specs.md. Toute modif de src/core/api.ts, src/core/tokens.ts ou des hooks doit y rester alignée.

Dev local du package

npm run dev          # tsup watch
npm run build        # bundle dist/
npm run typecheck
npm test             # vitest watch
npm run test:run     # vitest single run