Skip to content

@flowsta/auth

OAuth 2.0 authentication SDK for web applications.

@flowsta/auth handles the complete OAuth 2.0 + PKCE flow for browser-based apps. Zero dependencies, TypeScript-first, with React bindings included.

Installation

bash
npm install @flowsta/auth

Quick Start

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

const auth = new FlowstaAuth({
  clientId: 'your_client_id',
  redirectUri: 'https://yourapp.com/auth/callback'
});

// Redirect to Flowsta login
auth.login();

// On your callback page
const user = await auth.handleCallback();
console.log('Welcome,', user.displayName);

Configuration

typescript
interface FlowstaAuthConfig {
  clientId: string;        // Required - from dev.flowsta.com
  redirectUri: string;     // Required - OAuth callback URL
  scopes?: string[];       // Optional - default: ['openid', 'email', 'display_name']
  loginUrl?: string;       // Optional - default: 'https://login.flowsta.com'
  apiUrl?: string;         // Optional - default: 'https://auth-api.flowsta.com'
}

No Client Secret Required

Flowsta uses PKCE (Proof Key for Code Exchange) which provides security without client secrets. Works safely in browsers and mobile apps.

Methods

login()

Redirect user to Flowsta's login page.

typescript
auth.login();

handleCallback()

Handle the OAuth callback and retrieve user data. Call this on your redirect URI page.

typescript
const user = await auth.handleCallback();

isAuthenticated()

Check if user is logged in.

typescript
if (auth.isAuthenticated()) {
  const user = auth.getUser();
}

getUser()

Get the current user profile.

typescript
const user = auth.getUser();
// { id, email?, username?, displayName?, profilePicture?, agentPubKey?, did?, linkedAgents?, signingMode? }

getAccessToken()

Get the current access token.

typescript
const token = auth.getAccessToken();

getState()

Get the full authentication state.

typescript
const state = auth.getState();
// { isAuthenticated, user, accessToken, isLoading, error }

logout()

Clear the local session.

typescript
auth.logout();

detectVault()

Check if Flowsta Vault is running locally.

typescript
const vault = await auth.detectVault();
// { running: boolean, agentPubKey?: string, did?: string }
if (vault.running) {
  console.log('Vault agent:', vault.agentPubKey);
}

getLinkedAgents(agentPubKey?)

Get agents linked to a specific agent, or the current user's agent if no key is provided. Returns an array of agent public key strings.

typescript
// Get agents linked to current user
const agents = await auth.getLinkedAgents();
// ['uhCAk...', 'uhCAk...']

// Get agents linked to a specific agent
const agents = await auth.getLinkedAgents('uhCAk7Jp...');

areAgentsLinked(agentA, agentB)

Check if two agents are linked via Flowsta identity.

typescript
const linked = await auth.areAgentsLinked(agentKeyA, agentKeyB);

Type Definitions

typescript
interface FlowstaUser {
  id: string;
  email?: string;
  username?: string;
  displayName?: string;
  profilePicture?: string;
  agentPubKey?: string;              // Holochain agent public key
  did?: string;                      // W3C Decentralized Identifier
  linkedAgents?: LinkedAgent[];      // Linked agents (DHT-verified)
  signingMode?: 'remote' | 'ipc';   // 'remote' = API, 'ipc' = Vault
}

interface LinkedAgent {
  agentPubKey: string;     // The linked agent's public key
  linkedAt?: string;       // When the link was created
  isRevoked: boolean;      // Whether the link has been revoked
}

interface VaultDetectionResult {
  running: boolean;        // Whether Vault is running
  agentPubKey?: string;    // Vault agent's public key (if unlocked)
  did?: string;            // Vault agent's DID (if unlocked)
}

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

React Integration

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

function App() {
  return (
    <FlowstaAuthProvider
      clientId="your_client_id"
      redirectUri="https://yourapp.com/auth/callback"
    >
      <MyApp />
    </FlowstaAuthProvider>
  );
}

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>;
}

Provider Required

useFlowstaAuth() must be used within a FlowstaAuthProvider. If used outside, it will throw: "useFlowstaAuth must be used within a FlowstaAuthProvider".

useRequireAuth()

Protect routes by redirecting unauthenticated users. Returns an isReady flag that is true once the user is confirmed authenticated.

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

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

  if (!isReady) {
    return <div>Loading...</div>;
  }

  return <div>Welcome, {user?.displayName}!</div>;
}

Error Handling

handleCallback() throws specific errors you can catch:

typescript
try {
  const user = await auth.handleCallback();
} catch (error) {
  switch (true) {
    case error.message.includes('No authorization code'):
      // User cancelled login or error from Flowsta
      break;
    case error.message.includes('Invalid state'):
      // CSRF attack or expired session
      break;
    case error.message.includes('Missing PKCE code verifier'):
      // Session was cleared before callback (e.g. different browser tab)
      break;
    case error.message.includes('Token exchange failed'):
      // Code expired (10 min limit) or server error
      break;
    case error.message.includes('Failed to fetch user info'):
      // Token was issued but userinfo request failed
      break;
    default:
      console.error('Authentication failed:', error.message);
  }
}

Security

The SDK automatically handles:

  • PKCE - Generates code_verifier and code_challenge for each login
  • State parameter - Random state stored in sessionStorage for CSRF protection
  • Secure storage - Access and refresh tokens in localStorage, PKCE/state in sessionStorage (cleared after use)
  • Session restoration - Authenticated state persists across page reloads via localStorage
  • Refresh tokens - Stored in localStorage as flowsta_refresh_token for long-lived sessions

Migrating from SDK 1.x

SDK 2.0 removes direct email/password authentication. All users now authenticate through Flowsta's hosted login page.

Before (SDK 1.x):

typescript
// No longer supported
await auth.login(email, password);

After (SDK 2.0+):

typescript
// OAuth redirect
auth.login(); // Redirects to login.flowsta.com
const user = await auth.handleCallback();

Next Steps

Documentation licensed under CC BY-SA 4.0.