@ciromaciel/auth-react

中文文档
1.16.0发布于 2026/11/05MITTypeScriptESM + CJS零依赖近 30 天下载 3453

Auth SDK for React: JWT sessions, passwordless sign-in, and ready-made Mantine screens.

原始使用文档
文档目录18 节
  1. Installation
  2. Quick start
  3. Signing in
  4. Social sign-in
  5. Recent accounts
  6. Several accounts, one browser
  7. The owner's timeline
  8. The account card
  9. The account screen
  10. API
  11. Components
  12. Routing
  13. Conditional rendering
  14. Unstyled buttons
  15. Hooks
  16. Direct SDK calls
  17. Features
  18. Links

@ciromaciel/auth-react

npm version npm downloads license

Passwordless authentication for React: a code arrives by email, and it becomes a JWT session.

There is one way in. No password, no sign-up screen, no reset flow — the account is created and verified the first time a code is presented. Social sign-in is available when the application owner enables a provider.

Installation

bun add @ciromaciel/auth-react

Peer dependencies: react, react-dom, react-router-dom and zustand. The pre-built screens also need @mantine/core, @mantine/form, @mantine/hooks and @tabler/icons-react — all optional if you build your own UI from the hooks.

Quick start

import { AuthProvider, Protect, SignIn, SignedIn, SignedOut, SignInButton } from '@ciromaciel/auth-react'

function App() {
    return (
        <AuthProvider apiKey="your-api-key">
            <Routes>
                <Route path="/login" element={<SignIn />} />
                <Route element={<Protect redirectTo="/login" />}>
                    <Route path="/" element={<Home />} />
                </Route>
            </Routes>
        </AuthProvider>
    )
}

function Header() {
    return (
        <header>
            <SignedIn>
                <UserMenu />
            </SignedIn>
            <SignedOut>
                <SignInButton />
            </SignedOut>
        </header>
    )
}

AuthProvider takes apiKey (required), apiUrl, internal, onError, appearance and optimistic. In internal mode the API key is not required — that is for same-origin applications.

With optimistic (the default, since 1.10.0) a stored session that has not expired opens the screen at once, and Auth confirms it in the background: a session Auth refuses signs out as soon as it answers. Pass optimistic={false} to keep the loading screen until Auth answers. An impersonated session always waits.

Signing in

Two steps, and nothing else:

import { useSignIn } from '@ciromaciel/auth-react'

const { requestCode, verifyCode, sending, verifying, error } = useSignIn()

await requestCode(email) // a code goes out by email
await verifyCode(email, code) // the code becomes a session

Or use the ready-made screen:

<SignIn
    authenticatedRedirect="/" // where to send someone who already has a session
    onCodeSent={email => notify(`Code sent to ${email}`)}
    onSuccess={(user, { result, redirectHandled }) => notify(`Welcome ${user?.email}`)}
    onError={(error, { isShownOnCard }) => {
        // A wrong, expired or exhausted code is already explained on the card
        if (!isShownOnCard) notify(error.message) // error.code carries the stable identifier
    }}
/>
  • onSuccess receives the user first; the raw API response is result.
  • redirectHandled is true when a ?redirect= in the URL was already applied. The component honours it by default, validating the destination against the API origin and the current one; redirectOrigins adds domains and handleRedirect={false} hands control back to the app.
  • A wrong code fails with error.details.attemptsLeft. Five wrong tries destroy the request and the person asks for a new code.
  • On the code step the card explains every failure itself: how many tries are left, or, once the code is exhausted or expired, a button that sends a new one. onError still fires, with a second argument { step: 'request' | 'verify', isShownOnCard }; skip your own notification when isShownOnCard is true, or the person reads the same failure twice.

Social sign-in

<SignIn /> renders the provider buttons on its own: socialLogin defaults to 'auto', which shows whatever the application owner enabled and nothing at all when they enabled none.

<SignIn />                      {/* buttons appear when a provider is live */}
<SignIn socialLogin={false} />  {/* emailed code only */}

Building your own screen? getSocialProviders() lists what is live, startSocialSignIn(provider, { redirect }) leaves for the provider, and consumeSocialToken() reads the token the callback leaves in the URL fragment — AuthProvider already calls that one for you. startSocialLink(), unlinkSocialProvider() and getLinkedProviders() manage the connections of an account that is already signed in.

Recent accounts

<SignIn /> remembers the accounts that signed in on this browser and, on the next visit, shows them before the email field. One click on an account sends the code (or goes back to the provider that account used last time) and opens the code step. With nothing saved, the screen is the plain email form.

  • An account is saved only after a session exists: the code confirmed, or the provider's token back. A mistyped email never becomes a suggestion.
  • The list keeps the five most recent accounts. Each entry holds the email, the way in and the time of the last sign-in.
  • Every app of the same application shares the list, even across origins: the Auth worker keeps it in an HttpOnly cookie on its own host and answers only to the origins the application allows. A copy in localStorage (auth:recent-accounts) renders at once while the shared list loads, and stands in when the worker cannot be reached.
  • Signing out keeps the list. "Gerenciar" on the screen removes an account from every app of the application; nothing else about that account changes.
<SignIn />                         {/* shows and saves recent accounts */}
<SignIn recentAccounts={false} />  {/* shared computers: neither shows nor saves */}

Building your own screen? fetchRecentAccounts(), saveRecentAccount(method) and deleteRecentAccount(email) talk to the shared list; listRecentAccounts(), rememberAccount(email, method), forgetAccount(email) and adoptRecentAccounts(list) manage the local copy.

Several accounts, one browser

A person with two accounts does not sign out of one to enter the other. Every account that signs in on a browser stays signed in, and switching between them takes one click and no code.

  • Nothing to wire. <UserInformation /> shows the other accounts under "Outras contas" and an "Adicionar conta" row; with more than one account, the exit becomes "Sair desta conta" and "Sair de todas as contas". <SignIn /> offers the accounts still signed in as rows that enter with "Conectada · entra sem código", above the recent ones.
  • Adding an account opens your sign-in route with ?mode=add. <GuestOnly> and <SignIn> let a signed-in person through in that mode, and the current account stays signed in. An app whose sign-in sits inside a page shows it when isAddingAccount() is true, and passes that page as signInUrl.
  • Switching is an identity switch: everything your app keeps in storage for the previous account is wiped (as with an impersonation, see IDENTITY_CHANGED_EVENT), the other tabs follow, and the page lands on afterSwitchUrl (default /), because the address on screen may be a resource only the previous account can open.
  • One account is active per browser at a time; every request still carries one token, so your backend changes nothing.
  • On the platform's zones the list lives behind an HttpOnly cookie. On your own domain the SDK keeps the browser's secret in localStorage (auth:browser), next to the session token.
<AuthProvider apiKey="pu_..." afterSwitchUrl="/" signInUrl="/auth/signin">
<UserInformation hasAccountSwitcher={false} />  {/* one account per browser */}
<AccountSwitcher onSignedOut={goToSignIn} />    {/* the same group, inside your own menu */}

const { accounts, otherAccounts, switchAccount, addAccount, signOutAccount, signOutAll } = useAccounts()

The owner's timeline

Nothing to wire: with AuthProvider in place, the owner of the application sees, on each user's page in the Auth panel, where the person came from and what they did.

  • The visit: the referrer from another site, utm_*, an ad's click id and the landing page, kept on the first screen and sent right after a sign-in.
  • The screens: each route opened, the path only, ids collapsed, at most once per path every 5 minutes.
  • The actions: every write your app sends to its own API (POST, PUT, PATCH, DELETE) with the person's token, once it succeeds. Only the method and the address travel, never the body or the answer. Writes to Auth itself are left out.
  • <AuthProvider activity={false}> turns screens and actions off. For a richer label, or an action with no browser (a CLI, a job), the backend can call reportActivity from @ciromaciel/contract/activity with the application's secret key.

The account card

<UserInformation /> is what opens from the footer of a sidebar: who is signed in, in which organization and on which plan, then one list of actions with "Sair" last.

<UserInformation
    user={user}
    signOut={signOut}
    onAccountClick={openAccount}
    onBillingClick={openBilling}
    items={[{ id: 'docs', label: 'Documentação', icon: IconFileText, onClick: openDocs }]}
    plan={{ name: 'Pro', used: 8, limit: 9, unit: 'projetos', canUpgrade: true, onClick: openPlans }}
    organization={{ name: 'Acme', role: 'owner' }}
/>
  • The title is the user's name, or the email when there is none — never both saying the same thing.

  • items are your app's own rows, passed as data ({ id, label, icon, onClick }), shown in their own group after billing. The component takes no JSX, so every app's card keeps the same shape. A row may add description (one line saying what it does) and tone: 'danger' (red, for what cannot be undone); itemsLabel titles the group:

    itemsLabel="Seus dados"
    items={[
        { id: 'export', label: 'Exportar', description: 'Uma cópia de tudo, em JSON', icon: IconDownload, onClick: exportAll },
        { id: 'erase', label: 'Apagar tudo', description: 'Sem volta', tone: 'danger', icon: IconTrash, onClick: eraseAll },
    ]}
    
  • plan and organization draw the context strip; leave them out and it is not drawn. limit: null hides the usage bar.

  • The strip informs; "Assinatura" is the way to billing. The strip adds a "Fazer upgrade" invite only when canUpgrade is true (there is a plan above this one) and usage is at 80% of the limit or more. At the limit, the bar and the count turn red and "Limite atingido" appears on the badge's line. plan.onClick is what the invite opens; actionLabel renames it.

  • The footer reads "Protegido por Auth" with a "Termos" link to the same terms the sign-in cites. termsUrl points it elsewhere, termsUrl={null} drops the link, and branded={false} removes the footer.

The account screen

<UserProfile /> is what "Conta" on the account card opens: the photo, the name and the email; the ways in; and the devices with an open session.

<UserProfile opened={opened} onClose={close} />
  • Every text defaults to Portuguese. labels changes a word (labels={{ edit: 'Editar' }}); a panel does not need it to translate the screen.
  • Name and photo are edited in place: "Alterar" opens the field on the same row, Enter saves, Esc cancels and leaves the screen open. The email does not change here, and says why: it is where the sign-in code arrives.
  • "Formas de entrar" lists the emailed code, which always works, and the providers the application enabled, with "Conectar" and "Desconectar". With none enabled, the group is not drawn. showSignInMethods={false} hides it.
  • The sessions sit on one line ("2 sessões abertas", and which one is this device); a click opens the list right below. The current device comes first and cannot be ended from here — that is "Sair". "Encerrar as outras" asks before ending them.
  • Failures are shown on the screen, next to what failed. onError still receives the error, with { section, isShownOnScreen: true }; do not show it a second time.

API

Components

ComponentDescription
<SignIn />The whole way in: asks for the email, then takes the code
<UserProfile />The account screen: profile, ways in, open sessions
<UserInformation />The account card: who, where, which plan, then the actions
<SocialButtons />Sign-in buttons for the providers the application enabled
<AuthCard />The card shell the screens are built on
<Wordmark />Application wordmark

Routing

ComponentDescription
<Protect />Route wrapper; redirects to redirectTo without a session
<GuestOnly />The counterpart: keeps a signed-in person off the login screen

Conditional rendering

ComponentDescription
<SignedIn>Renders children only when authenticated
<SignedOut>Renders children only when NOT authenticated
<AuthLoading>Renders children while auth is loading
<AuthLoaded>Renders children once auth has loaded

Unstyled buttons

ComponentDescription
<SignInButton />Navigates to sign-in page
<SignOutButton />Signs out the user

Hooks

const { user, loading, error, isAuthenticated, requestCode, verifyCode, signOut } = useAuth()
const { user, updateProfile, loadingUpdateProfile } = useUser()
const { requestCode, verifyCode, sending, verifying, error } = useSignIn()
const { getSession, user, setUser } = useSession()
const { sessions, listSessions, revokeSession, revokeOtherSessions } = useSessions()
const loadingStates = useAuthLoading()
const impersonation = useImpersonation() // the borrowed session, or null
const { accounts, otherAccounts, switchAccount, addAccount } = useAccounts() // the accounts of this browser
const logo = useApplicationLogo()
const signOut = useSignOut()
const checkToken = useCheckToken()

Direct SDK calls

For code outside React, the HTTP layer is exported too: configure, requestCode, verifyCode, pollCode, signOut, refreshToken, getSession, listSessions, revokeSession, revokeOtherSessions, updateProfile, getApplicationInfo, isAuthenticated, getCurrentUser, decodeJWT, setStoredToken, and the social helpers listed above.

Features

  • Passwordless — an emailed code, for people and agents alike; no password to leak
  • JWT sessions with automatic refresh
  • JWKS signature verification via /.well-known/jwks.json
  • Cross-tab sync — sign out in one tab, every tab follows
  • Route protection through <Protect /> and <GuestOnly />
  • SSR friendly

MIT © Ciro Cesar Maciel

版本与发布记录

@ciromaciel/auth-react 共发布 32 个版本,首次发布于 2026/09/20;过去 12 个月发布了 32 个版本。

版本发布日期距今
1.16.0latest2026/11/054 天前
1.15.02026/11/054 天前
1.14.02026/11/045 天前
1.12.02026/11/045 天前
1.11.22026/10/2515 天前
1.11.12026/10/2416 天前

关于 @ciromaciel/auth-react 的常见问题

@ciromaciel/auth-react 是什么?

Auth SDK for React: JWT sessions, passwordless sign-in, and ready-made Mantine screens.

如何安装 @ciromaciel/auth-react?

在项目目录运行 npm install @ciromaciel/auth-react,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(^18.0.0 || ^19.0.0)、zustand(^5.0.0)、react-dom(^18.0.0 || ^19.0.0)、react-router-dom(^6.0.0 || ^7.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@ciromaciel/auth-react 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@ciromaciel/auth-react 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@ciromaciel/auth-react 可以商用吗?

@ciromaciel/auth-react 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@ciromaciel/auth-react 的下载量有多少?

根据 npm 官方下载统计,@ciromaciel/auth-react 在 2026/10/10 – 2026/11/08 这 30 天内被下载 3,453 次,日均约 115 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@ciromaciel/auth-react 还在维护吗?

最新版本 1.16.0 发布于 2026/11/05(4 天前);至今共发布 32 个版本,其中过去 12 个月发布了 32 个;首次发布于 2026/09/20。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。