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

azirid-access

v0.4.0

Published

Authentication components for React and Next.js — Login, Register, powered by TanStack Query and Zod.

Readme

azirid-access

Authentication components and hooks for React and Next.js — powered by TanStack Query and Zod.

Drop-in <LoginForm>, <SignupForm> and more, or use the headless hooks to build fully custom UIs.

Installation

npm install azirid-access
# or
pnpm add azirid-access
# or
yarn add azirid-access

Peer dependencies

npm install react react-dom @tanstack/react-query
# Tailwind CSS is optional – only needed if you use the built-in components

Quick start

1. Wrap your app with <AziridProvider>

// app/layout.tsx (Next.js App Router) or main.tsx (Vite/CRA)
import { AziridProvider } from 'azirid-access'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <AziridProvider
      publishableKey={process.env.NEXT_PUBLIC_AZIRID_PK!}
      tenantId="tenant_abc"
      onLoginSuccess={(data) => console.log('Logged in:', data.user)}
      onLogoutSuccess={() => console.log('Logged out')}
      onSessionExpired={() => (window.location.href = '/login')}
    >
      {children}
    </AziridProvider>
  )
}

2. Add the built-in forms

import { LoginForm, SignupForm } from 'azirid-access'

// Login page
export default function LoginPage() {
  return (
    <LoginForm
      onSuccess={(data) => {
        console.log('User:', data.user)
      }}
    />
  )
}

// Signup page
export default function SignupPage() {
  return <SignupForm />
}

Headless hooks

All hooks require <AziridProvider> in the tree.

useAzirid — session state

import { useAzirid } from 'azirid-access'

function Navbar() {
  const { user, isAuthenticated, isLoading, login, logout } = useAzirid()

  if (isLoading) return <Spinner />

  return isAuthenticated ? (
    <div>
      <span>Hello, {user!.email}</span>
      <button onClick={logout}>Sign out</button>
    </div>
  ) : (
    <button onClick={() => login({ email: '...', password: '...' })}>Sign in</button>
  )
}

useLogin

import { useLogin } from 'azirid-access'

function CustomLoginForm() {
  const { login, isLoading, error } = useLogin({
    onSuccess: (data) => console.log(data.user),
    onError: (msg) => console.error(msg),
  })

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault()
        const fd = new FormData(e.currentTarget)
        login({
          email: fd.get('email') as string,
          password: fd.get('password') as string,
        })
      }}
    >
      <input name="email" type="email" />
      <input name="password" type="password" />
      {error && <p>{error}</p>}
      <button disabled={isLoading}>Sign in</button>
    </form>
  )
}

useSignup

import { useSignup } from 'azirid-access'

const { signup, isLoading, error } = useSignup({
  onSuccess: (data) => console.log('Registered:', data.user),
})

signup({ email: '[email protected]', password: 'secret', name: 'Alice' })

useLogout

import { useLogout } from 'azirid-access'

const { logout, isLoading } = useLogout({
  onSuccess: () => router.push('/login'),
})

useSession

import { useSession } from 'azirid-access'

const { user, accessToken, isAuthenticated } = useSession()

useMagicLink

import { useMagicLink } from 'azirid-access'

const { requestMagicLink, verifyMagicLink, isLoading } = useMagicLink()

requestMagicLink({ email: '[email protected]' })
verifyMagicLink({ token: '...' })

useSocialLogin

import { useSocialLogin } from 'azirid-access'

const { loginWithProvider, isLoading } = useSocialLogin()

loginWithProvider({ provider: 'google' }) // "google" | "github"

usePasskeys

import { usePasskeys } from 'azirid-access'

const { passkeys, registerPasskey, removePasskey, isLoading } = usePasskeys()

useChangePassword

import { useChangePassword } from 'azirid-access'

const { changePassword, isLoading, error } = useChangePassword()

changePassword({ currentPassword: 'old', newPassword: 'new' })

useBootstrap

Manually re-run the session bootstrap (useful after SSO redirects).

import { useBootstrap } from 'azirid-access'

const { bootstrap, isBootstrapping } = useBootstrap()

useRefresh

Manually refresh the access token.

import { useRefresh } from 'azirid-access'

const { refresh } = useRefresh()

useAziridClient

Access the raw AccessClient instance for custom API calls.

import { useAziridClient } from 'azirid-access'

function CustomAction() {
  const client = useAziridClient()

  async function fetchCustomData() {
    const data = await client.get('/v1/custom-endpoint')
    console.log(data)
  }

  return <button onClick={fetchCustomData}>Fetch</button>
}

useFormState

Headless form hook with Zod validation. Powers the built-in form components — use it to build fully custom forms.

import { useFormState, loginSchema } from 'azirid-access'

function CustomForm() {
  const { values, errors, isSubmitting, handleChange, handleSubmit, reset } = useFormState(
    { email: '', password: '' },
    loginSchema,
    async (values) => {
      // Submit logic
    },
  )

  return (
    <form onSubmit={handleSubmit}>
      <input value={values.email} onChange={handleChange('email')} />
      {errors.find((e) => e.field === 'email')?.message}
      <button disabled={isSubmitting}>Submit</button>
    </form>
  )
}

usePasswordToggle

Simple toggle between "password" and "text" input types.

import { usePasswordToggle } from 'azirid-access'

function PasswordInput() {
  const { visible, toggle, type } = usePasswordToggle()

  return (
    <div>
      <input type={type} name="password" />
      <button type="button" onClick={toggle}>
        {visible ? 'Hide' : 'Show'}
      </button>
    </div>
  )
}

Internationalization (i18n)

Built-in support for English and Spanish. The SDK ships two complete dictionaries; pass a locale prop to switch languages.

import { AziridProvider } from 'azirid-access'
;<AziridProvider publishableKey="pk_live_..." locale="en">
  {/* All form labels, validation messages, and UI text render in English */}
  {children}
</AziridProvider>

Supported locales

| Locale | Language | | ------ | ----------------- | | "es" | Spanish (default) | | "en" | English |

Custom messages

Override any string by passing a partial messages object:

<AziridProvider
  publishableKey="pk_live_..."
  locale="en"
  messages={{
    login: { title: 'Welcome back!', submit: 'Sign in' },
    validation: { emailRequired: 'Please enter your email' },
  }}
>
  {children}
</AziridProvider>

Using i18n hooks directly

import { useMessages, useBranding } from 'azirid-access'

function CustomForm() {
  const msg = useMessages() // resolved messages for current locale
  return <label>{msg.login.emailLabel}</label>
}

Locale-aware Zod schemas

import { createLoginSchema, createSignupSchema } from 'azirid-access'

// Pass custom validation messages
const schema = createLoginSchema({
  emailRequired: 'Email is required',
  emailInvalid: 'Must be a valid email',
  passwordRequired: 'Password is required',
  passwordMin: 'At least 8 characters',
})

Branding

The bootstrap endpoint returns branding data configured in the Azirid dashboard (Settings > Branding). The built-in form components automatically apply branding.

Auto-branding from bootstrap

If branding is configured for your app, the forms will automatically:

  • Show your logo (from branding.logoUrl) above the form
  • Use your display name as the form title
  • Apply your primary color to the submit button
  • Show/hide the "Secured by Azirid" badge

No extra code needed — just configure branding in the dashboard.

Overriding branding with props

Per-component props always take priority over branding context:

<LoginForm
  logo={<MyCustomLogo />} // overrides branding.logoUrl
  title="Sign in to Acme" // overrides branding.displayName
  submitText="Continue"
/>

Using branding hooks

import { useBranding } from 'azirid-access'

function CustomHeader() {
  const branding = useBranding() // AppBranding | null

  return (
    <div>
      {branding?.logoUrl && <img src={branding.logoUrl} alt="Logo" />}
      <h1 style={{ color: branding?.primaryColor ?? '#000' }}>
        {branding?.displayName ?? 'My App'}
      </h1>
    </div>
  )
}

"Secured by Azirid" badge

The <SecuredByBadge /> component renders below each form. It's hidden when branding.removeBranding is true (configurable in the dashboard).

import { SecuredByBadge } from "azirid-access";

// Use in custom form layouts
<form>
  {/* ... your form fields ... */}
</form>
<SecuredByBadge />

createAccessClient

Under the hood AziridProvider creates an AccessClient via createAccessClient. You can also create a client directly to make raw API calls.

import { createAccessClient, PATHS } from 'azirid-access'
import type { AccessClientConfig } from 'azirid-access'

const config: AccessClientConfig = {
  baseUrl: 'https://api.azirid.com/v1',
  headers: { 'X-Custom': 'value' }, // optional extra headers
}

const client = createAccessClient(config, {
  publishableKey: 'pk_live_...',
  tenantId: 'tenant_abc', // optional
})

// Set tokens after login
client.setAccessToken('eyJ...')
client.setRefreshToken('...')

// Make arbitrary authenticated calls
const data = await client.get(client.paths.me)
const result = await client.post('/v1/custom-endpoint', { foo: 'bar' })

createAccessClient signature

function createAccessClient(
  config: AccessClientConfig,
  appContext?: { publishableKey: string; tenantId?: string },
): AccessClient

| Param | Type | Description | | ------------ | -------------------- | ------------------------------------------------------- | | config | AccessClientConfig | { baseUrl: string; headers?: Record<string, string> } | | appContext | object | Optional. publishableKey and tenantId |


AziridProvider props

| Prop | Type | Default | Description | | ------------------ | ------------------------- | -------- | ---------------------------------------------------------------------------------------------- | | children | ReactNode | — | Required. Your app tree | | publishableKey | string | — | Publishable key (e.g. pk_live_...) | | tenantId | string | — | Tenant ID for multi-tenant apps | | fetchOptions | Record<string, string> | — | Extra headers to send with every request | | autoBootstrap | boolean | true | Auto-restore session on mount | | refreshInterval | number | 50000 | Token refresh interval in ms. 0 to disable | | sessionSyncUrl | string \| false | auto | URL for session cookie sync. Auto-activates in dev mode. Pass false to disable | | onLoginSuccess | (data) => void | — | Called after successful login | | onSignupSuccess | (data) => void | — | Called after successful signup | | onLogoutSuccess | () => void | — | Called after logout | | onSessionExpired | () => void | — | Called when refresh fails | | onError | (msg: string) => void | — | Called on any auth error | | locale | "es" \| "en" | "es" | UI language for built-in forms and validation messages | | messages | Partial<AccessMessages> | — | Override any i18n string (merged on top of the locale dictionary) |


Next.js Integration

azirid-access supports Next.js 14, 15, and 16+ with full compatibility for each version's API conventions.

Proxy Route Handler (all versions)

Create the file app/v1/auth/[...path]/route.ts — one line is all you need:

// app/v1/auth/[...path]/route.ts
export { GET, POST, PUT, PATCH, DELETE } from 'azirid-access/next'

That's it. The library handles all the proxy logic, cookie fixing, and header forwarding internally. It works with Next.js 14, 15, and 16+ automatically.

If you need a custom API URL or debug logging:

// app/v1/auth/[...path]/route.ts
import { createAziridRouteHandlers } from 'azirid-access/next'

export const { GET, POST, PUT, PATCH, DELETE } = createAziridRouteHandlers({
  apiUrl: 'https://my-custom-api.com',
  debug: true, // logs proxy requests to console
})

Next.js Config

Next.js 16+ (next.config.ts)

Turbopack is the default bundler — transpilePackages is no longer needed:

// next.config.ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {}

export default nextConfig

Next.js 14/15 (next.config.js)

// next.config.js
const { withAziridProxy } = require('azirid-access/next')

/** @type {import('next').NextConfig} */
module.exports = withAziridProxy()({
  transpilePackages: ['azirid-access'],
})

Route Protection (optional)

Not required for basic usage. Only add this if you need to protect specific routes from unauthenticated users.

Next.js 16+ (proxy.ts)

// proxy.ts — only needed if you want route protection
import { createAziridProxy } from 'azirid-access/next'

export const proxy = createAziridProxy({
  protectedRoutes: ['/dashboard', '/settings'],
  loginUrl: '/login',
  publicRoutes: ['/login', '/signup', '/forgot-password'],
})

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

Next.js 14/15 (middleware.ts)

// middleware.ts — only needed if you want route protection
import { createAziridMiddleware } from 'azirid-access/next'

export default createAziridMiddleware({
  protectedRoutes: ['/dashboard', '/settings'],
  loginUrl: '/login',
  publicRoutes: ['/login', '/signup', '/forgot-password'],
})

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

Server-side (Next.js App Router)

For Server Components, Server Actions, and Route Handlers, use the azirid-access/server entry point to read the session token from the httpOnly __session cookie.

Setup

// lib/access-server.ts
import { cookies } from 'next/headers'
import { createServerAccess } from 'azirid-access/server'

// Works with all Next.js versions:
// - Next.js 14: cookies() returns a sync cookie store
// - Next.js 15/16+: cookies() returns a Promise — handled automatically
export const { getSessionToken, getAccessToken } = createServerAccess({ cookies })

Server Action example

// app/actions/profile.ts
'use server'
import { getSessionToken } from '@/lib/access-server'

export async function getProfile() {
  const token = await getSessionToken()
  if (!token) throw new Error('Not authenticated')

  const res = await fetch(`${process.env.API_URL}/v1/users/auth/me`, {
    headers: { Authorization: `Bearer ${token}` },
  })
  return res.json()
}

Server Component example

// app/dashboard/page.tsx
import { redirect } from 'next/navigation'
import { getSessionToken } from '@/lib/access-server'

export default async function DashboardPage() {
  const token = await getSessionToken()
  if (!token) redirect('/login')

  const res = await fetch(`${process.env.API_URL}/v1/users/auth/me`, {
    headers: { Authorization: `Bearer ${token}` },
  })
  const user = await res.json()

  return <h1>Hello, {user.email}</h1>
}

Options

| Option | Type | Default | Description | | ------------ | -------- | ------------- | ------------------------------------------ | | cookieName | string | "__session" | Name of the httpOnly cookie with the token |

createSessionSyncHandler

Creates a route handler that syncs the access token to a local httpOnly cookie. Useful for cross-origin development setups where the API is on a different domain.

// app/api/auth/session/route.ts
import { createSessionSyncHandler } from 'azirid-access/server'

export const { POST, DELETE } = createSessionSyncHandler()

With custom options:

export const { POST, DELETE } = createSessionSyncHandler({
  cookieName: '__session', // default
  secure: true, // set Secure flag on cookie
  maxAge: 3600, // cookie max age in seconds (default: 1h)
})

| Option | Type | Default | Description | | ------------ | --------- | ------------- | ----------------------------------- | | cookieName | string | "__session" | Name of the httpOnly cookie | | secure | boolean | false | Set the Secure flag on the cookie | | maxAge | number | 3600 | Cookie max age in seconds |


Version Compatibility

| Feature | Next.js 14 | Next.js 15 | Next.js 16+ | | ------------------- | ---------------- | ------------------- | ------------------- | | React | 18.x | 18.x / 19.x | 19.x+ | | Node.js | >= 18.0.0 | >= 18.17.0 | >= 20.9.0 | | Config file | next.config.js | next.config.js/ts | next.config.ts | | Request interceptor | middleware.ts | middleware.ts | proxy.ts | | cookies() | sync | async (with compat) | async only | | params | sync | async (with compat) | async only | | Bundler | Webpack | Webpack/Turbopack | Turbopack (default) | | transpilePackages | Required | Required | Not needed |


Tailwind CSS (optional)

The built-in form components (LoginForm, SignupForm, etc.) use Tailwind utility classes. Add azirid-access to your content glob so Tailwind picks them up.

// tailwind.config.js
module.exports = {
  content: ['./src/**/*.{ts,tsx}', './node_modules/azirid-access/dist/**/*.{js,mjs}'],
}

License

MIT © Azirid