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

@ovixa/auth-client

v0.4.2

Published

Client SDK for Ovixa Auth service

Readme

@ovixa/auth-client

Client SDK for the Ovixa Auth service. Provides authentication, token verification, and session management for applications using Ovixa's centralized identity provider.

Features

  • Email/password authentication (signup, login, password reset)
  • Passkey (WebAuthn) authentication - phishing-resistant passwordless sign-in
  • OAuth integration (Google, GitHub)
  • JWT verification with JWKS caching
  • Automatic token refresh
  • Framework integrations (Astro, Express)
  • TypeScript-first with full type definitions

Installation

npm install @ovixa/auth-client
# or
pnpm add @ovixa/auth-client

Quick Start

import { OvixaAuth } from '@ovixa/auth-client';

const auth = new OvixaAuth({
  authUrl: 'https://auth.example.com',
  realmId: 'your-realm-id',
});

// Sign up a new user
await auth.signup({
  email: '[email protected]',
  password: 'SecurePassword123!',
  redirectUri: 'https://yourapp.com/verify-callback',
});

// Log in and get tokens
const tokens = await auth.login({
  email: '[email protected]',
  password: 'SecurePassword123!',
});

// Convert to AuthResult for easier handling
const result = await auth.toAuthResult(tokens);
console.log('User:', result.user.email);
console.log('Expires:', result.session.expiresAt);

// Verify a JWT token
const verified = await auth.verifyToken(tokens.access_token);
console.log('User ID:', verified.payload.sub);

API Reference

OvixaAuth

Main client class for interacting with the Ovixa Auth service.

Constructor

new OvixaAuth(config: AuthClientConfig)

| Option | Type | Required | Description | | -------------- | -------- | -------- | ------------------------------------------- | | authUrl | string | Yes | Base URL of the Ovixa Auth service | | realmId | string | Yes | The realm ID to authenticate against | | clientSecret | string | No | Client secret (for server-side use) | | jwksCacheTtl | number | No | JWKS cache duration in ms (default: 1 hour) |

Authentication Methods

signup(options)

Create a new user account. A verification email is sent after signup.

await auth.signup({
  email: '[email protected]',
  password: 'SecurePassword123!',
  redirectUri: 'https://yourapp.com/verify-callback', // Optional
});

login(options)

Authenticate with email and password.

const tokens = await auth.login({
  email: '[email protected]',
  password: 'SecurePassword123!',
});
// Returns: { access_token, refresh_token, token_type, expires_in }

logout(refreshToken)

Revoke a refresh token to log out.

await auth.logout(refreshToken);

Email Verification

verifyEmail(options)

Verify email using a token (returns tokens for automatic login).

const tokens = await auth.verifyEmail({
  token: 'verification-token-from-email',
});

resendVerification(options)

Resend the verification email.

await auth.resendVerification({
  email: '[email protected]',
  redirectUri: 'https://yourapp.com/verify-callback', // Optional
});

Password Reset

forgotPassword(options)

Request a password reset email.

await auth.forgotPassword({
  email: '[email protected]',
  redirectUri: 'https://yourapp.com/reset-password', // Optional
});

Email Branding

Verification and password reset emails automatically display your realm's display_name as the brand name. To customize the branding in emails:

  1. Set display_name when creating your realm
  2. Emails will show your brand (e.g., "Linkdrop") instead of "Ovixa"

If no display_name is set, emails default to "Ovixa".

resetPassword(options)

Set a new password using a reset token.

await auth.resetPassword({
  token: 'reset-token-from-email',
  password: 'NewSecurePassword123!',
});

Token Management

verifyToken(token)

Verify an access token and return the decoded payload.

const result = await auth.verifyToken(accessToken);
console.log('User ID:', result.payload.sub);
console.log('Email:', result.payload.email);
console.log('Verified:', result.payload.email_verified);

refreshToken(refreshToken)

Exchange a refresh token for new tokens.

const newTokens = await auth.refreshToken(currentRefreshToken);
// Store the new tokens - refresh token rotation is used

toAuthResult(tokenResponse)

Convert a token response to a structured AuthResult with user and session data.

const tokens = await auth.login({ email, password });
const result = await auth.toAuthResult(tokens);

// result.user: { id, email, emailVerified }
// result.session: { accessToken, refreshToken, expiresAt }
// result.isNewUser?: boolean (for OAuth flows)

clearJwksCache()

Invalidate the cached JWKS to force a refresh on next verification.

auth.clearJwksCache();

Admin Operations

Admin operations require clientSecret to be configured. These operations allow server-side management of users within the realm boundary.

Important: Never expose clientSecret to client-side code.

admin.deleteUser(options)

Delete a user from your realm. This permanently deletes the user and all associated data (tokens, OAuth accounts).

const auth = new OvixaAuth({
  authUrl: 'https://auth.example.com',
  realmId: 'your-realm-id',
  clientSecret: process.env.OVIXA_CLIENT_SECRET, // Required
});

// Delete a user
await auth.admin.deleteUser({ userId: 'user-id-to-delete' });

Error Handling:

try {
  await auth.admin.deleteUser({ userId });
} catch (error) {
  if (error instanceof OvixaAuthError) {
    if (error.code === 'NOT_FOUND') {
      console.error('User not found');
    } else if (error.code === 'FORBIDDEN') {
      console.error('User does not belong to this realm');
    } else if (error.code === 'REALM_UNAUTHORIZED') {
      console.error('Invalid client secret');
    }
  }
}

Passkeys (WebAuthn)

Passkeys provide phishing-resistant, passwordless authentication using FIDO2/WebAuthn.

webauthn.getRegistrationOptions(options)

Get options for registering a new passkey. Requires authentication.

const options = await auth.webauthn.getRegistrationOptions({
  accessToken: 'user-access-token',
});
// Returns PublicKeyCredentialCreationOptions for navigator.credentials.create()

webauthn.verifyRegistration(options)

Verify and store a new passkey registration. Requires authentication.

const result = await auth.webauthn.verifyRegistration({
  accessToken: 'user-access-token',
  registration: credentialResponse, // From navigator.credentials.create()
  deviceName: 'My MacBook', // Optional friendly name
});
// Returns: { success: true, credential_id: string, device_name?: string }

webauthn.getAuthenticationOptions(options)

Get options for signing in with a passkey. Does not require authentication.

const options = await auth.webauthn.getAuthenticationOptions({
  email: '[email protected]', // Optional - provides allowCredentials hint
});
// Returns PublicKeyCredentialRequestOptions for navigator.credentials.get()

webauthn.authenticate(options)

Verify passkey authentication and receive tokens.

const tokens = await auth.webauthn.authenticate({
  authentication: credentialResponse, // From navigator.credentials.get()
});
// Returns: { access_token, refresh_token, token_type, expires_in }

Complete Passkey Flow Example

import { OvixaAuth } from '@ovixa/auth-client';

const auth = new OvixaAuth({
  authUrl: 'https://auth.example.com',
  realmId: 'your-realm-id',
});

// Register a passkey (user must be logged in)
async function registerPasskey(accessToken: string) {
  // 1. Get registration options from server
  const options = await auth.webauthn.getRegistrationOptions({ accessToken });

  // 2. Create credential using browser API
  const credential = await navigator.credentials.create({
    publicKey: options,
  });

  if (!credential) throw new Error('Registration cancelled');

  // 3. Verify with server
  const result = await auth.webauthn.verifyRegistration({
    accessToken,
    registration: credential as PublicKeyCredential,
    deviceName: 'My Device',
  });

  return result;
}

// Sign in with passkey
async function signInWithPasskey(email?: string) {
  // 1. Get authentication options
  const options = await auth.webauthn.getAuthenticationOptions({ email });

  // 2. Get credential using browser API
  const credential = await navigator.credentials.get({
    publicKey: options,
  });

  if (!credential) throw new Error('Authentication cancelled');

  // 3. Verify and get tokens
  const tokens = await auth.webauthn.authenticate({
    authentication: credential as PublicKeyCredential,
  });

  return tokens;
}

OAuth

getOAuthUrl(options)

Generate an OAuth authorization URL.

const googleUrl = auth.getOAuthUrl({
  provider: 'google', // or 'github'
  redirectUri: 'https://yourapp.com/auth/callback',
});

// Redirect user to start OAuth flow
window.location.href = googleUrl;

After OAuth completes, the user is redirected to your redirectUri with tokens as URL hash parameters:

// Handle callback in your app
const hash = new URLSearchParams(window.location.hash.slice(1));
const accessToken = hash.get('access_token');
const refreshToken = hash.get('refresh_token');

Framework Integrations

Astro Middleware

// src/middleware.ts
import { createAstroAuth } from '@ovixa/auth-client/astro';
import { OvixaAuth } from '@ovixa/auth-client';

const auth = new OvixaAuth({
  authUrl: import.meta.env.AUTH_URL,
  realmId: import.meta.env.AUTH_REALM_ID,
});

export const onRequest = createAstroAuth({
  auth,
  publicRoutes: ['/', '/login', '/signup', '/api/public/*'],
  loginRedirect: '/login',
  cookies: {
    secure: import.meta.env.PROD,
  },
});

Access auth context in pages:

---
// src/pages/dashboard.astro
const { user, isAuthenticated } = Astro.locals.auth;

if (!isAuthenticated) {
  return Astro.redirect('/login');
}
---

<h1>Welcome, {user.email}</h1>

Set cookies after login:

// src/pages/api/login.ts
import { setAstroAuthCookies } from '@ovixa/auth-client/astro';
import type { APIContext } from 'astro';

export async function POST({ request, cookies }: APIContext) {
  const { email, password } = await request.json();
  const tokens = await auth.login({ email, password });

  setAstroAuthCookies({ cookies }, tokens);

  return new Response(JSON.stringify({ success: true }));
}

Clear cookies on logout:

// src/pages/api/logout.ts
import { clearAstroAuthCookies } from '@ovixa/auth-client/astro';

export async function POST({ cookies, locals }: APIContext) {
  if (locals.auth?.session?.refreshToken) {
    await auth.logout(locals.auth.session.refreshToken);
  }

  clearAstroAuthCookies({ cookies });

  return new Response(JSON.stringify({ success: true }));
}

Express Middleware

import express from 'express';
import cookieParser from 'cookie-parser';
import { createExpressAuth, requireAuth } from '@ovixa/auth-client/express';
import { OvixaAuth } from '@ovixa/auth-client';

const auth = new OvixaAuth({
  authUrl: process.env.AUTH_URL!,
  realmId: process.env.AUTH_REALM_ID!,
});

const app = express();
app.use(cookieParser());
app.use(
  createExpressAuth({
    auth,
    publicRoutes: ['/', '/login', '/signup'],
    loginRedirect: '/login',
    cookies: {
      secure: process.env.NODE_ENV === 'production',
    },
  })
);

// Access auth context in routes
app.get('/api/me', (req, res) => {
  if (!req.auth?.isAuthenticated) {
    return res.status(401).json({ error: 'Unauthorized' });
  }
  res.json({ user: req.auth.user });
});

// Use requireAuth middleware for protected routes
app.get('/dashboard', requireAuth({ redirect: '/login' }), (req, res) => {
  res.render('dashboard', { user: req.auth.user });
});

Set cookies after login:

import { setExpressAuthCookies } from '@ovixa/auth-client/express';

app.post('/api/login', async (req, res) => {
  const { email, password } = req.body;
  const tokens = await auth.login({ email, password });

  setExpressAuthCookies(res, req, tokens);

  res.json({ success: true });
});

Custom Framework Integration

Implement the CookieAdapter interface to add support for other frameworks:

import type {
  CookieAdapter,
  SetCookieOptions,
  DeleteCookieOptions,
} from '@ovixa/auth-client/astro';

class HonoCookieAdapter implements CookieAdapter {
  constructor(private context: HonoContext) {}

  getCookie(name: string): string | undefined {
    return this.context.req.cookie(name);
  }

  setCookie(name: string, value: string, options: SetCookieOptions): void {
    this.context.cookie(name, value, options);
  }

  deleteCookie(name: string, options: DeleteCookieOptions): void {
    this.context.cookie(name, '', { ...options, maxAge: 0 });
  }
}

Middleware Configuration

| Option | Type | Default | Description | | --------------- | ----------- | -------- | ---------------------------------------- | | auth | OvixaAuth | Required | The OvixaAuth client instance | | publicRoutes | string[] | [] | Routes that bypass authentication | | loginRedirect | string | - | URL to redirect unauthenticated requests | | autoRefresh | boolean | true | Automatically refresh expired tokens | | cookies | object | - | Cookie configuration (see below) |

Cookie Options

| Option | Type | Default | Description | | -------------------- | --------- | ----------------------- | ------------------------------- | | accessTokenCookie | string | 'ovixa_access_token' | Name of access token cookie | | refreshTokenCookie | string | 'ovixa_refresh_token' | Name of refresh token cookie | | path | string | '/' | Cookie path | | secure | boolean | true | Use secure cookies (HTTPS only) | | httpOnly | boolean | true | HTTP-only cookies | | sameSite | string | 'lax' | SameSite policy | | domain | string | - | Cookie domain |

Error Handling

All methods throw OvixaAuthError on failure:

import { OvixaAuth, OvixaAuthError } from '@ovixa/auth-client';

try {
  await auth.login({ email, password });
} catch (error) {
  if (error instanceof OvixaAuthError) {
    console.error('Code:', error.code); // e.g., 'INVALID_CREDENTIALS'
    console.error('Message:', error.message);
    console.error('Status:', error.statusCode); // HTTP status code
  }
}

Error Codes

| Code | Description | | ------------------------- | ----------------------------------------------------- | | INVALID_CREDENTIALS | Wrong email or password | | EMAIL_NOT_VERIFIED | User must verify email before login | | PASSWORD_RESET_REQUIRED | Account flagged for mandatory password reset | | INVALID_TOKEN | Token is invalid or malformed | | TOKEN_EXPIRED | Token has expired | | INVALID_SIGNATURE | Token signature verification failed | | INVALID_ISSUER | Token issuer doesn't match | | INVALID_AUDIENCE | Token audience doesn't match | | RATE_LIMITED | Too many requests | | NETWORK_ERROR | Failed to reach auth service | | BAD_REQUEST | Invalid request parameters | | UNAUTHORIZED | Authentication required | | FORBIDDEN | Access denied (e.g., user belongs to different realm) | | NOT_FOUND | Resource not found | | SERVER_ERROR | Auth service error | | REALM_UNAUTHORIZED | Invalid or missing realm client secret | | CLIENT_SECRET_REQUIRED | Admin operation attempted without clientSecret config |

Handling PASSWORD_RESET_REQUIRED

When an administrator flags an account as compromised, login and token refresh will return a 403 error with code PASSWORD_RESET_REQUIRED. The user must complete a password reset before they can log in again.

try {
  const tokens = await auth.login({ email, password });
} catch (error) {
  if (error instanceof OvixaAuthError) {
    if (error.code === 'PASSWORD_RESET_REQUIRED') {
      // Redirect user to password reset flow
      await auth.forgotPassword({ email });
      // Show message: "Your account requires a password reset. Check your email."
      return;
    }
    // Handle other errors...
  }
}

This also applies to token refresh - if you're using automatic token refresh in middleware, handle this error to redirect users to the password reset flow:

try {
  const newTokens = await auth.refreshToken(refreshToken);
} catch (error) {
  if (error instanceof OvixaAuthError && error.code === 'PASSWORD_RESET_REQUIRED') {
    // Clear cookies and redirect to login with a message
    clearAuthCookies();
    redirect('/login?reason=password_reset_required');
  }
}

Types

// Token response from auth service
interface TokenResponse {
  access_token: string;
  refresh_token: string;
  token_type: 'Bearer';
  expires_in: number;
  is_new_user?: boolean; // OAuth flows only
}

// User information (extracted from JWT claims)
interface User {
  id: string;
  email: string;
  emailVerified: boolean;
  // Note: createdAt is intentionally omitted. The JWT `iat` claim is when the
  // *token* was issued, not when the user was created. If you need the user's
  // creation date, fetch it from the /me endpoint or include it in custom claims.
}

// Session data
interface Session {
  accessToken: string;
  refreshToken: string;
  expiresAt: Date;
}

// Combined auth result
interface AuthResult {
  user: User;
  session: Session;
  isNewUser?: boolean;
}

// Auth context (available in middleware)
interface AuthContext {
  user: User | null;
  session: Session | null;
  isAuthenticated: boolean;
}

License

MIT