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

@everystack/security

v0.2.0

Published

Device attestation, biometric auth, and cryptographic identity for Expo apps

Readme

@everystack/security

Device attestation (Apple App Attest, Google Play Integrity), RS256 JWT, JWKS, challenge-response verification, and cryptographic utilities for Expo apps.

Install

pnpm add @everystack/security drizzle-orm

Entry Points

| Import | Description | |--------|-------------| | @everystack/security | Server-side: handler, verifier, stores, JWT, crypto | | @everystack/security/client | Client-side: attestation providers, biometric auth | | @everystack/security/schema | Drizzle tables (challenges, device_keys) | | @everystack/security/crypto | Hash (SHA-256) and random utilities |

Server: Verifier

The verifier handles the challenge-response flow for device attestation:

import { createVerifier } from '@everystack/security';

const verifier = createVerifier({
  apple: {
    appId: 'TEAM_ID.com.example.app',
    development: process.env.NODE_ENV !== 'production',
  },
  google: {
    packageName: 'com.example.app',
    decryptionKey: process.env.GOOGLE_DECRYPTION_KEY,
    verificationKey: process.env.GOOGLE_VERIFICATION_KEY,
  },
  challengeTtlMs: 5 * 60 * 1000, // 5 minutes
});

// 1. Client requests a challenge
const { challengeId, challenge, expiresAt } = await verifier.createChallenge(deviceId);

// 2. Client performs attestation with the challenge, sends result back

// 3. Server verifies attestation
const result = await verifier.verifyAttestation('ios', attestationData, challengeId, keyId);
// → { keyId, publicKey, counter, environment, appId }

// 4. For subsequent requests, verify assertions
const assertion = await verifier.verifyAssertion(assertionData, clientData, keyId);
// → { valid: true, counter }

Certificate Chain Validation

The Apple attestation verifier validates the full certificate chain from the leaf certificate through the intermediate to Apple's root CA. This ensures attestation responses genuinely originate from Apple's servers.

In production, the chain is validated using @peculiar/x509 — forged certificates are rejected. For development/testing, chain validation can be explicitly bypassed:

const verifier = createVerifier({
  apple: {
    appId: 'TEAM_ID.com.example.app',
    skipChainValidation: true, // Only for development — never in production
  },
});

The nonce extension is extracted from the attestation certificate using a proper ASN.1 parser (by OID), not raw byte pattern matching.

Persistent Stores

Use Drizzle-backed stores for production (in-memory stores are for testing):

import {
  createVerifier,
  DrizzleChallengeStore,
  DrizzleKeyStore,
} from '@everystack/security';

const verifier = createVerifier({
  apple: { appId: 'TEAM_ID.com.example.app' },
  challengeStore: new DrizzleChallengeStore(db),
  keyStore: new DrizzleKeyStore(db),
});

Server: HTTP Handler

Expose attestation endpoints as a Web Standard handler:

import { createSecurityHandler } from '@everystack/security';

const handler = createSecurityHandler({
  verifierConfig: {
    apple: { appId: 'TEAM_ID.com.example.app' },
    challengeStore: new DrizzleChallengeStore(db),
    keyStore: new DrizzleKeyStore(db),
  },
  jwt: {
    current: { kid: 'key-1', publicKey, privateKey },
    previous: [], // For key rotation
  },
  basePath: '/api/security',
  auth: { verifyToken: async (token) => verifyJWT(token) },
});

Endpoints

| Method | Path | Description | |--------|------|-------------| | POST | /challenges | Create a new challenge | | POST | /verify | Verify device attestation | | POST | /assert | Verify assertion (subsequent requests) | | GET | /.well-known/jwks.json | JWKS public key endpoint |

Mounting

// app/api/security/[...path]+api.ts
export function GET(request: Request) { return handler(request); }
export function POST(request: Request) { return handler(request); }

JWT

RS256 JWT signing, verification, and key management:

import { signJWT, verifyJWT, generateKeyPair, getJWKS } from '@everystack/security';

// Generate RSA key pair
const { publicKey, privateKey } = await generateKeyPair();

// Sign a token
const token = await signJWT(
  { sub: 'user-123', role: 'admin' },
  privateKey,
  { kid: 'key-1', expiresIn: '1h' },
);

// Verify a token
const payload = await verifyJWT(token, publicKey);
// → { sub: 'user-123', role: 'admin', iat: ..., exp: ... }

// Decode without verification (for debugging)
const decoded = decodeJWT(token);

// Generate JWKS for public key distribution
const jwks = getJWKS([{ kid: 'key-1', publicKey, privateKey }]);
// Serve at /.well-known/jwks.json

Key Rotation

const handler = createSecurityHandler({
  jwt: {
    current: { kid: 'key-2', publicKey: newKey, privateKey: newPrivate },
    previous: [
      { kid: 'key-1', publicKey: oldKey, privateKey: oldPrivate },
    ],
  },
});
// JWKS includes both keys; tokens signed with either are valid

Crypto Utilities

import { sha256, sha256Bytes } from '@everystack/security/crypto';
import { randomUUID, randomBytes, randomHex } from '@everystack/security/crypto';

// Hashing
const hash = await sha256('hello world');       // hex string
const bytes = await sha256Bytes('hello world'); // Uint8Array

// Random generation
const uuid = randomUUID();            // UUID v4
const bytes = randomBytes(32);        // 32 random bytes
const hex = randomHex(16);            // 16-byte hex string

Runtime Requirements

All random generation functions require crypto.randomUUID() or crypto.getRandomValues() to be available. There is no Math.random() fallback — if the Web Crypto API is unavailable, the functions throw a descriptive error.

This is intentional: Math.random() is not cryptographically secure and must never be used for security-sensitive IDs (tokens, challenge nonces, key identifiers).

React Native: The Web Crypto API is available in Hermes (React Native 0.74+). For older versions, install a polyfill such as react-native-get-random-values and import it before any @everystack/security imports:

import 'react-native-get-random-values'; // Must be first
import { randomUUID } from '@everystack/security/crypto';

Stores

Challenge Store

Manages attestation challenges with TTL:

interface ChallengeStore {
  create(deviceId: string | undefined, ttlMs: number): Promise<Challenge>;
  verify(id: string, challenge: string): Promise<boolean>;
}

Implementations:

  • InMemoryChallengeStore — For testing
  • DrizzleChallengeStore — PostgreSQL via Drizzle (uses atomic UPDATE ... WHERE revokedAt IS NULL RETURNING * to prevent race conditions — a challenge can only be consumed once, even under concurrent requests)

Key Store

Stores device public keys for assertion verification:

interface KeyStore {
  save(key: DeviceKey): Promise<void>;
  get(keyId: string): Promise<DeviceKey | null>;
  updateCounter(keyId: string, counter: number): Promise<void>;
}

Implementations:

  • InMemoryKeyStore — For testing
  • DrizzleKeyStore — PostgreSQL via Drizzle

Schema

Add security tables to your Drizzle migrations:

import { challenges, deviceKeys } from '@everystack/security/schema';

Client SDK

For React Native apps, the client SDK provides attestation providers:

import { AttestationProvider, useBiometricAuth } from '@everystack/security/client';

See the client entry point for device attestation flow integration with Expo.

Peer Dependencies

| Package | Version | Required | |---------|---------|----------| | drizzle-orm | >=0.35.0 | For Drizzle stores | | expo-constants | >=16.0.0 | Client SDK | | expo-local-authentication | >=14.0.0 | Biometric auth | | react | >=18.0.0 | Client SDK | | react-native | >=18.0.0 | Client SDK |


Part of everystack — a self-hosted application stack for Expo apps on AWS.

License

MIT