Skip to content

algorandfoundation/liquid-auth-core

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

2 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ’§ Liquid Auth Core

Warning

This project is experimental and under active development. APIs are subject to change without notice. Use with caution in production environments.

@algorandfoundation/liquid-auth-core is a modular, environment-agnostic middleware suite for implementing Liquid Auth. It provides authentication, FIDO (WebAuthn), and WebRTC signaling capabilities, built on top of itty-router to run anywhere from Node.js Express to Cloudflare Workers.

πŸš€ Installation

npm install @algorandfoundation/liquid-auth-core

Note: This package is ESM-only. Ensure your package.json includes "type": "module" or use .mjs extensions.

πŸ› οΈ Configuration

The middleware is configured using a common LiquidAuthOptions object:

import { LiquidAuthOptions } from '@algorandfoundation/liquid-auth-core';

const options: LiquidAuthOptions = {
  origin: 'https://your-domain.com', // Base URL of your app
  storage: myStorageAdapter,        // Required: user and session persistence
  events: myEventsAdapter,          // Optional: for cross-device signaling
  logger: myLoggingAdapter,          // Optional: structured logging
  rpID: 'your-domain.com',          // WebAuthn Relying Party ID
  rpName: 'Your App Name',          // WebAuthn Relying Party Name
  extensions: [myCustomExtension],  // Optional: custom signature verifiers
  sessionCookieName: 'liquid_sid'   // Optional: defaults to 'liquid_sid'
};

πŸ“¦ Environments

Liquid Auth Core is built using itty-router, making it compatible with various environments.

πŸš„ Usage with Express

For Express applications, we provide an adapter and a composed middleware.

Full Middleware

Mounts both auth and fido capabilities.

import express from 'express';
import { liquidAuthMiddleware } from '@algorandfoundation/liquid-auth-core';

const app = express();
app.use(express.json());

// Options as defined above
app.use('/auth', liquidAuthMiddleware(options));

Manual Composition

If you need more control, you can mount them individually:

import { auth, fido, createExpressMiddleware } from '@algorandfoundation/liquid-auth-core';

app.use('/auth', createExpressMiddleware(auth(options)));
app.use('/auth/fido', createExpressMiddleware(fido(options)));

🌍 Usage in Agnostic Environments (itty-router)

You can use the routers directly in any environment that supports Request/Response (Fetch API).

import { auth, fido } from '@algorandfoundation/liquid-auth-core';
import { Router } from 'itty-router';

const router = Router();

// Mount the liquid auth routers
router.all('/auth/*', auth(options).handle);
router.all('/auth/fido/*', fido(options).handle);

export default {
  fetch: (req, env, ctx) => router.handle(req)
};

πŸ” Capabilities

πŸ†” FIDO (WebAuthn)

Handles WebAuthn attestation (registration) and assertion (login) flows.

Routes:

  • POST /attestation/request: Generates WebAuthn registration options.
    • Body: { username: string, extensions?: { liquid?: boolean } }
    • If extensions.liquid is true, it returns verificationMethods (e.g. ["ed25519"]) supported by the service.
  • POST /attestation/response: Verifies registration and creates/updates the user.
    • Body: WebAuthn response + optional wallet or address for the linked identifier.
  • POST /assertion/request/:credId: Generates authentication options for a specific credential.
    • Body: { extensions?: { liquid?: boolean } }
  • POST /assertion/response: Verifies authentication and starts the user session.

πŸ‘€ User Auth

Handles user profile retrieval, session status, and logout.

Routes:

  • GET /user: Returns the profile of the currently authenticated user.
  • GET /session: Returns the current session state and associated user info.
  • GET /logout: Destroys the authentication session and redirects to /.
  • DELETE /keys/:id: Deletes a specific WebAuthn credential from the user's account.

πŸ“‘ Signaling

Provides a WebSocket handler for WebRTC signaling between devices (e.g., browser and mobile wallet).

Basic Usage

import { signal } from '@algorandfoundation/liquid-auth-core/signal';
import { WebSocketAdapter } from '@algorandfoundation/liquid-auth-core/adapters';
import { WebSocketServer } from 'ws';

const wss = new WebSocketServer({ server });
const handler = signal(options);

wss.on('connection', (ws, req) => {
  const adapter = new WebSocketAdapter(ws);
  handler(adapter, req);
});

Sharing Express Sessions

To enable cross-device linking, the signaling handler needs access to the user's session. If you are using express-session, you can apply it to the WebSocket upgrade request:

wss.on('connection', (ws, req) => {
  // sessionMiddleware is your express-session instance
  sessionMiddleware(req, {}, () => {
    const adapter = new WebSocketAdapter(ws);
    handler(adapter, req);
  });
});

WebSocket Events:

  • link: Orchestrates the connection between a browser session and a wallet device.
  • offer-description / answer-description: Forwards WebRTC SDP messages to the linked peer.
  • offer-candidate / answer-candidate: Forwards WebRTC ICE candidates to the linked peer.

πŸͺ Session Management

Liquid Auth Core is environment-agnostic regarding sessions. It prioritizes existing session objects but can manage its own if needed.

  1. Express Session (Recommended for Express): If req.session is present (e.g., via express-session), the middleware will use it to store authentication state.
  2. Managed Sessions (Agnostic): If no session object is found on the request, the core will:
    • Look for a session ID in the liquid_sid cookie (configurable).
    • Retrieve session data using storage.findSession(sid).
    • If missing, create a new session and persist it via storage.saveSession(sid, data).
    • Automatically set the Set-Cookie header in the response.

This allows the same code to work in Express, Cloudflare Workers, and other environments without changing the core logic.

πŸ”Œ Adapters

Liquid Auth Core uses adapters to remain platform-agnostic. Each adapter has a specific interface you must implement or use a provided one.

πŸ’Ύ StorageAdapter

Handles persistence for users, credentials, and session associations. Required by all middleware components.

export interface StorageAdapter {
  findUserByWallet(wallet: string): Promise<LiquidUser | null>;
  findUserByCredId(credId: string): Promise<LiquidUser | null>;
  createUser(wallet: string): Promise<LiquidUser>;
  updateUser(user: LiquidUser): Promise<void>;
  findSession(sid: string): Promise<any | null>;
  saveSession?(sid: string, data: any): Promise<void>;
  updateSessionWallet(sid: string, wallet: string): Promise<void>;
  getAccountAuthAddress?(address: string): Promise<string | null>; // Optional rekeying check
}

export interface LiquidUser {
  id: string; // Internal user ID
  wallet: string; // Primary wallet address
  credentials: LiquidCredential[];
}

export interface LiquidCredential {
  credId: string;
  publicKey: string;
  prevCounter: number;
  device?: string;
}

πŸ”” EventsAdapter

Used to bridge authentication events (like a successful FIDO login) to other parts of the system, such as the signaling handler.

export interface EventsAdapter {
  emit(event: string, data: any): void;
  on(event: string, callback: (data: any) => void): void;
  off(event: string, callback: (data: any) => void): void;
}

πŸ“‘ SocketAdapter

An abstraction over different WebSocket implementations (e.g., native ws). The core package provides a WebSocketAdapter for the standard ws library.

export interface SocketAdapter {
  id?: string;
  on(event: string, listener: (...args: any[]) => void): this;
  once(event: string, listener: (...args: any[]) => void): this;
  emit(event: string, ...args: any[]): this;
  disconnect(): this;
  removeAllListeners(event?: string): this;
}

πŸ“ LoggingAdapter

Provides a structured way to handle logs. If not provided, a no-op logger is used.

export interface LoggingAdapter {
  info(message: string, ...args: any[]): void;
  error(message: string, ...args: any[]): void;
  warn?(message: string, ...args: any[]): void;
  debug?(message: string, ...args: any[]): void;
}

🧩 LiquidExtensionAdapter

Allows for custom verification logic of the liquid extension signatures during WebAuthn registration.

export interface LiquidExtensionAdapter {
  type: string; // The identifier for the signature type (e.g., 'ed25519')
  verify(
    challenge: string,
    signature: string,
    address: string,
    storage: StorageAdapter,
  ): Promise<boolean>;
}

πŸ“‚ Examples

For a complete implementation example using Express, sessions, and WebSockets, check the examples/express directory.

πŸ“„ License

Apache-2.0

About

Core library for Liquid Auth

Resources

License

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors