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

@flowsta/auth

v2.5.1

Published

Flowsta Auth SDK - OAuth authentication with Vault detection and agent linking

Readme

@flowsta/auth

Flowsta Auth SDK - OAuth 2.0 authentication with PKCE, Vault detection, and agent linking for web applications.

Features

  • 🔐 OAuth 2.0 + PKCE - Secure authentication without client secrets
  • 🌐 Zero-Knowledge - Your users' data stays private
  • Simple Integration - Just a few lines of code
  • 🎨 Framework Support - Vanilla JS, React, and more
  • 🔗 Agent Linking - Query Holochain agent identity links
  • 🖥️ Vault Detection - Detect local Flowsta Vault desktop app
  • ✍️ Sign It - Sign files on behalf of the authenticated user (requires sign scope)

Installation

npm install @flowsta/auth
# or
yarn add @flowsta/auth
# or
pnpm add @flowsta/auth

Quick Start

1. Create an App

Go to dev.flowsta.com and create an app to get your Client ID.

2. Redirect to Login

import { FlowstaAuth } from '@flowsta/auth';

const auth = new FlowstaAuth({
  clientId: 'your-client-id',
  redirectUri: 'https://yoursite.com/auth/callback',
});

// Redirect to Flowsta login
document.getElementById('login-btn').onclick = () => auth.login();

3. Handle the Callback

On your redirect URI page (/auth/callback):

import { FlowstaAuth } from '@flowsta/auth';

const auth = new FlowstaAuth({
  clientId: 'your-client-id',
  redirectUri: 'https://yoursite.com/auth/callback',
});

// Handle the OAuth callback
try {
  const user = await auth.handleCallback();
  console.log('Logged in as:', user.displayName);
  window.location.href = '/dashboard';
} catch (error) {
  console.error('Login failed:', error.message);
}

4. Check Authentication Status

if (auth.isAuthenticated()) {
  const user = auth.getUser();
  console.log('Welcome,', user.displayName);
}

React Integration

import { FlowstaAuthProvider, useFlowstaAuth } from '@flowsta/auth/react';

// Wrap your app
function App() {
  return (
    <FlowstaAuthProvider
      clientId="your-client-id"
      redirectUri="https://yoursite.com/auth/callback"
    >
      <MyApp />
    </FlowstaAuthProvider>
  );
}

// Use in components
function LoginButton() {
  const { isAuthenticated, user, login, logout } = useFlowstaAuth();

  if (isAuthenticated) {
    return (
      <div>
        <span>Hello, {user.displayName}!</span>
        <button onClick={logout}>Logout</button>
      </div>
    );
  }

  return <button onClick={login}>Sign in with Flowsta</button>;
}

Protected Routes

import { useRequireAuth } from '@flowsta/auth/react';

function ProtectedPage() {
  const { isReady, user } = useRequireAuth();
  // Or redirect to a custom URL: useRequireAuth({ redirectTo: '/login' })

  if (!isReady) return <div>Loading...</div>;
  return <div>Welcome, {user?.displayName}!</div>;
}

Sign It

Let your users sign files with their Flowsta identity. Hashing happens in the browser — files are never uploaded.

Add the sign scope when you construct FlowstaAuth:

const flowsta = new FlowstaAuth({
  clientId: 'your_client_id',
  redirectUri: 'https://your-app.com/callback',
  scopes: ['openid', 'email', 'display_name', 'sign'],
});

Users see "This app wants to sign files on your behalf" in the consent screen.

Sign a file

import { FlowstaAuth, hashFile } from '@flowsta/auth';

const file = fileInput.files[0];
const hash = await hashFile(file);

const result = await flowsta.signFile({
  fileHash: hash,
  intent: 'authorship',
  contentRights: {
    license: 'cc-by',
    ai_training: 'not_allowed',
    contact_preference: 'allow_contact_requests',
  },
});

console.log('Action hash:', result.action_hash);
console.log('Signed at:', result.signed_at);

Verify a file

const { signatures, count } = await flowsta.verifyFile(hash);
if (count > 0) {
  console.log(`${count} signer(s):`);
  signatures.forEach((s) => console.log(s.signer, s.revoked ? '(revoked)' : ''));
}

Check content rights

const rights = await flowsta.getContentRights(hash);
if (rights.signed) {
  rights.rights.forEach((r) => {
    console.log(r.signer, '→', r.license, '/', r.aiTraining);
  });
}

See the full Sign It documentation for field values, content-rights spec, and the verification API.

API Reference

FlowstaAuth

const auth = new FlowstaAuth({
  clientId: string;      // Required: Your app's client ID
  redirectUri: string;   // Required: OAuth callback URL
  scopes?: string[];     // Optional: default ['openid', 'display_name'] - email is opt-in at both ends (v2.3.2+)
  loginUrl?: string;     // Optional: Flowsta login URL
  apiUrl?: string;       // Optional: Flowsta API URL
});

Methods

| Method | Returns | Description | |--------|---------|-------------| | login() | Promise<void> | Redirect to Flowsta login | | handleCallback() | Promise<FlowstaUser> | Handle OAuth callback | | logout() | void | Clear local session | | isAuthenticated() | boolean | Check if user is logged in | | getUser() | FlowstaUser \| null | Get current user | | getAccessToken() | string \| null | Get access token | | getState() | AuthState | Get full auth state | | detectVault() | Promise<VaultDetectionResult> | Check if Flowsta Vault is running locally (sweeps ports 27777-27779 and caches the answer, v2.4.0). Since v2.5.0 the result carries blocked: true when the BROWSER refused the loopback request (Chrome 142+ Local Network Access permission, Brave) - the Vault may be running; don't tell the user to install one | | getLinkedAgents(agentPubKey?) | Promise<string[]> | Get agents linked to current user or a specific agent | | areAgentsLinked(agentA, agentB) | Promise<boolean> | Check if two agents are linked | | signFile(options) | Promise<SignResult> | Sign a file hash (requires sign scope). Signs through Flowsta Vault when it's running (the user approves in the Vault); falls back to the API for legacy custodial accounts. Throws VaultRequiredError when neither can sign, and VaultIdentityMismatchError (v2.4.0) when the local Vault holds a DIFFERENT identity than the signed-in user - a signature must never come from someone else's Vault. The file is never uploaded. | | signBatch(options) | Promise<SignBatchResult> | Sign multiple hashes in one request (legacy custodial API only — with the Vault, call signFile() per file so the user approves each) | | verifyFile(hash) | Promise<VerifyResult> | Check if a hash has been signed. Public endpoint. | | getContentRights(hash) | Promise<ContentRightsResult> | Return just the declared content-rights for a hash |

Which browsers reach the Vault (verified 2026-08)

detectVault() and Vault-first signing call http://127.0.0.1 from your page. Chromium (Chrome 142+ behind a one-time Local Network Access prompt) and Firefox allow it; Safari blocks it (WebKit mixed-content), Brave blocks it silently unless the site is on Brave's allowlist, phones have no Vault. A denied permission looks like an absent Vault - detectVault().blocked / VaultBlockedError tell them apart (loopbackPermissionState() is exported too). For the non-reaching cases use the relay login in @flowsta/holochain.

Utilities

| Export | Returns | Description | |--------|---------|-------------| | hashFile(file) | Promise<string> | SHA-256 hex hash of a File, computed entirely in the browser | | agentKeysMatch(a, b) | boolean \| null | Compare two agent keys across their base64url and base58 encodings (uhCAk... appears in both). null = can't decode either side (v2.4.0) |

Errors

All errors extend FlowstaAuthError (which carries .code):

| Error | When | Suggested UX | |-------|------|--------------| | VaultRequiredError | Signing requested but no Vault can sign for this account | "Install or unlock Flowsta Vault" | | VaultBlockedError (2.5.0) | The browser refused this page's request to reach the Vault (Local Network Access permission denied; Brave's localhost block) - the Vault may be running | "Allow this site to reach apps on your device in the browser's site settings" | | VaultIdentityMismatchError (2.4.0) | The local Vault is signed in as a different identity than this session's user - refused before any signature is made | "Unlock the Vault for THIS account, or sign out" | | UserDeniedError | The user rejected the request in the Vault | "Signing cancelled" |

FlowstaUser

interface FlowstaUser {
  id: string;
  email?: string;              // Legacy custodial accounts only — device-hosted
                               // accounts (the norm) never expose an email; the
                               // server stores only a hash. Ask the user if you
                               // need one.
  username?: string;           // User's username (if set)
  displayName?: string;        // Display name
  profilePicture?: string;     // Profile picture URL
  agentPubKey?: string;        // Holochain agent public key
  did?: string;                // W3C Decentralized Identifier
  linkedAgents?: LinkedAgent[];// Linked agents (DHT-verified)
  signingMode?: 'remote' | 'ipc'; // Observed at sign-in; signFile() re-probes live
}

interface LinkedAgent {
  agentPubKey: string;
  linkedAt?: string;
  isRevoked: boolean;
}

interface VaultDetectionResult {
  running: boolean;
  agentPubKey?: string;
  did?: string;
}

interface AuthState {
  isAuthenticated: boolean;
  user: FlowstaUser | null;
  accessToken: string | null;
  isLoading: boolean;
  error: string | null;
}

Security

This SDK uses OAuth 2.0 Authorization Code Flow with PKCE, which means:

  • ✅ No client secrets needed (safe for browser/mobile apps)
  • ✅ Authorization codes are protected by PKCE challenge
  • ✅ State parameter prevents CSRF attacks
  • ✅ Access tokens stored in localStorage
  • ✅ PKCE verifiers stored in sessionStorage (cleared after use)

Documentation

License

MIT