Skip to content

UnlikeOtherAI/UnlikeOtherAuthenticator

Repository files navigation

Unlike Other Authenticator

Admin dashboard Domains and secrets Organisations directory
Organisation pre-approved users Teams directory Users directory
User detail Connection errors Feature groups

A centralized OAuth & authentication service designed to provide unified login across multiple products with configurable branding, UI, and security features.

What It Does

Unlike Other Authenticator is a stateless, API-first authentication service that enables:

  • Unified authentication across 4–5+ products with a single account per email
  • Multiple auth methods: Email/password, Google, Apple, Facebook, GitHub, LinkedIn
  • Configurable branding: Per-client UI theming, logos, colors, and language support
  • Optional 2FA: TOTP-based two-factor authentication
  • Secure configuration: Tamper-proof JWT-based config delivery
  • Zero admin UI: Client onboarding through signed configuration only

How It Works

Trust Model

  1. Client Identification: Each client is identified by a verified domain. Backend-only calls use per-domain client secrets managed in Admin > Domains & Secrets.
  2. Config Delivery: All client configuration is delivered as a signed JWT. The OAuth server verifies the JWT signature before trusting any config.
  3. OAuth Flow: Uses the standard authorization code flow. Client popup redirects with a code, which the client backend exchanges for an access token.
  4. Token Pair: Access tokens remain short-lived JWTs (15–60 minutes). Client backends also receive rotating refresh tokens for server-side session renewal.

Core Principles

  • Email is the canonical user identifier
  • No email enumeration protection across all flows
  • All client config is signed and verified
  • Everything UI-related is templated and config-driven
  • No avatars stored locally (external URLs only)
  • Generic error messages only (no information leakage)

Configuration Options

Configuration is delivered as a signed JWT with the following properties:

Required Fields

  • domain — Client domain (e.g., app.example.com)
  • redirect_urls — Array of allowed OAuth redirect URLs
  • enabled_auth_methods — Array of enabled methods: ["email_password", "google", "apple", "facebook", "github", "linkedin"]; social provider names here are both enabled and allowed
  • ui_theme — Complete theme object (colors, radii, typography, logo URL, density)
  • language_config — Single language string or array of language codes

Optional Fields

  • 2fa_enabled — Boolean to enable/disable 2FA (default: false)
  • debug_enabled — Boolean to enable debug endpoints (default: false)
  • user_scope"global" (default) or "per_domain" (isolate users per domain)
  • language — Selected language (must be in language_config if provided)
  • org_features — Organisation/team/group feature configuration (see below)

Organisation Features (Optional)

Enable organisations, teams, and groups by adding org_features to the config:

{
  "org_features": {
    "enabled": true,
    "groups_enabled": false,
    "max_teams_per_org": 100,
    "max_members_per_org": 1000,
    "max_team_memberships_per_user": 50,
    "org_roles": ["owner", "admin", "member"]
  }
}

When enabled, the access token JWT includes an org claim with the user's organisation, team, and group memberships. Groups are managed exclusively through the Internal API (/internal/org/) using signed requests. See Section 24 of the brief for the full specification.

JWT Signing

All config JWTs must be signed and include the client domain. Expected claims:

  • domain — Client domain; must match the hostname of config_url
  • exp — Optional expiration (configs are verified on every request)

Installation

Prerequisites

  • Node.js 18+ and npm
  • PostgreSQL 14+
  • Social provider OAuth credentials (Google, Apple, Facebook, GitHub, LinkedIn)
  • SMTP server (optional, for email functionality)

1. Clone the Repository

git clone https://github.com/yourusername/unlike-other-authenticator.git
cd unlike-other-authenticator

2. Install Dependencies

npm install

This installs dependencies for both the API and Auth workspaces.

3. Environment Variables

Create .env files in both /API and /Auth directories.

API Environment Variables (/API/.env)

# Required
SHARED_SECRET=your-secret-key-here
DATABASE_URL=postgresql://user:password@localhost:5432/auth_db

# Social OAuth Providers
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
# ... (Apple, Facebook, GitHub, LinkedIn credentials)

# Email Service (optional)
EMAIL_PROVIDER=smtp  # or 'disabled'
EMAIL_FROM=noreply@yourservice.com
EMAIL_REPLY_TO=support@yourservice.com
SMTP_HOST=smtp.yourprovider.com
SMTP_PORT=587
SMTP_SECURE=false
SMTP_USER=your-smtp-username
SMTP_PASSWORD=your-smtp-password

# Optional Configuration
ACCESS_TOKEN_TTL=30  # minutes (15-60)
LOG_RETENTION_DAYS=90

4. Database Setup

# Generate Prisma client
npm run prisma:generate --workspace API

# Run database migrations
npm run prisma:migrate:dev --workspace API

5. Run Development Servers

# Terminal 1: Run API server
npm run dev:api

# Terminal 2: Run Auth UI
npm run dev:auth

The API runs on http://localhost:3000 by default. The Auth UI runs on http://localhost:5173 by default.

6. Build for Production

npm run build

This builds both API and Auth workspaces.

7. Run in Production

# Apply production migrations
npm run prisma:migrate:deploy --workspace API

# Start API server
npm run start --workspace API

Architecture

Project Structure

/API              — Node.js OAuth/auth server (Fastify)
/Auth             — React auth UI (Vite + Tailwind)
/Docs             — Full specification and architecture docs
  brief.md        — Complete product specification
  techstack.md    — Technology stack and structure
  Auth/
    architecture-api.md    — API layered architecture
    architecture-auth.md   — Auth UI component architecture
  Admin/           — Admin panel documentation
/Admin            — Admin panel UI (React CSR)
CLAUDE.md         — Agent/contributor instructions

API Architecture

The API follows a layered architecture:

Request → Routes → Middleware → Services → Database (Prisma)
  • Routes (/src/routes): Thin handlers that validate input and call services
  • Middleware (/src/middleware): Config verification, domain auth, error handling
  • Services (/src/services): Business logic for auth, users, tokens, social providers, email
  • Utils (/src/utils): Pure helper functions (hashing, validation, errors)

Key Rules:

  • No code file longer than 500 lines
  • Thin routes, fat services
  • All errors are generic to users, detailed in internal logs
  • Prisma for all database access

Auth UI Architecture

The Auth UI is a React application with config-driven theming:

PopupContainer
  └── ThemeProvider (config-driven)
      └── I18nProvider (language-driven)
          └── AuthLayout
              └── [Page Components]
  • Components (/src/components): Reusable UI primitives (buttons, cards, inputs) and auth forms
  • Pages (/src/pages): Auth flow screens (login, register, 2FA setup, etc.)
  • Theme (/src/theme): Maps config theme to Tailwind classes
  • i18n (/src/i18n): Translation loading with AI fallback for missing keys

Key Rules:

  • Tailwind-only styling (no other CSS frameworks)
  • All theming from config (no hardcoded brand styles)
  • One component per file for reusable components
  • No component file longer than 500 lines

Database Schema

Core Tables:

  • users — User accounts (email, password hash, name, pronouns preset/custom fields, avatar URL, 2FA settings)
  • domain_roles — Per-domain role assignments (superuser vs user)
  • login_logs — Audit trail of authentication events
  • verification_tokens — One-time tokens for email verification and password reset

Organisation Tables (opt-in via org_features config):

  • organisations — Tenant organisations, scoped per domain
  • org_members — User-to-org membership with configurable roles
  • teams — Named groups of users within an organisation
  • team_members — User-to-team membership with lead/member roles
  • groups — Named collections of teams (enterprise feature)
  • group_members — User-to-group membership with admin flag

User Scope:

  • Global (default): One email = one user across all domains
  • Per-domain: Same email on different domains = separate user records

Pronouns Model:

  • Use pronouns_preset plus optional pronouns_custom
  • Recommended presets: he_him, she_her, they_them, any_pronouns, ask_me, prefer_not_to_say, custom
  • Require pronouns_custom only when pronouns_preset = custom

Security Model

  • No email enumeration: All responses are generic ("Check your email")
  • Shared secret: Single global secret (never exposed, env var only)
  • Config integrity: All configs signed with JWT, verified on every request
  • Domain verification: Runs on each auth initiation (not cached)
  • Social email trust: Only provider-verified emails accepted
  • Short-lived access tokens: Access tokens expire in 15–60 minutes and are renewed through rotating refresh tokens
  • Generic errors: All user-facing error messages are non-specific

Development

Available Scripts

# Development
npm run dev:api          # Start API server in watch mode
npm run dev:auth         # Start Auth UI dev server

# Building
npm run build            # Build both workspaces

# Code Quality
npm run lint             # Lint all workspaces
npm run format           # Format code with Prettier
npm run typecheck        # TypeScript type checking

# Testing
npm run test             # Run tests in all workspaces

# Database
npm run prisma:generate --workspace API       # Generate Prisma client
npm run prisma:migrate:dev --workspace API    # Create and apply migration
npm run prisma:studio --workspace API         # Open Prisma Studio

Testing

# Run all tests
npm test

# Run API tests only
npm test --workspace API

# Watch mode (during development)
npm test -- --watch --workspace API

Tests are written using Vitest and cover:

  • Unit tests for all services
  • Integration tests for API endpoints
  • Security tests (enumeration protection, generic errors, token validation)

Client Integration

1. Generate Signed Config

On your client backend:

import jwt from 'jsonwebtoken';

const config = {
  domain: 'app.example.com',
  redirect_urls: ['https://app.example.com/auth/callback'],
  enabled_auth_methods: ['email_password', 'google'],
  ui_theme: {
    colors: { primary: '#3b82f6', secondary: '#64748b' },
    borderRadius: '0.5rem',
    // ... full theme config
  },
  language_config: ['en', 'es'],
  2fa_enabled: true,
  user_scope: 'global'
};

const configJWT = jwt.sign(config, process.env.CONFIG_PRIVATE_KEY_PEM, {
  algorithm: 'RS256',
  keyid: process.env.CONFIG_KEY_ID
});

// Serve this JWT at a URL accessible to the auth server

2. Initiate OAuth Flow

On your client frontend:

const configUrl = 'https://app.example.com/api/auth-config'; // serves the JWT
const authUrl = `https://auth.yourservice.com/oauth/authorize?config_url=${encodeURIComponent(configUrl)}`;

window.open(authUrl, 'oauth', 'width=500,height=700');

3. Handle OAuth Callback

On your client backend:

// Callback route receives the authorization code
app.get('/auth/callback', async (req, res) => {
  const { code } = req.query;

  // Exchange code for an access token + refresh token pair
  const response = await fetch('https://auth.yourservice.com/auth/token', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${hashDomainAndSecret(domain, sharedSecret)}`
    },
    body: JSON.stringify({
      code
    })
  });

  const { access_token, refresh_token } = await response.json();

  // Verify and decode the JWT access token
  const user = jwt.verify(access_token, process.env.SHARED_SECRET);

  // Store the refresh token server-side only (for example an HttpOnly cookie)
  res.cookie('refresh_token', refresh_token, {
    httpOnly: true,
    secure: true,
    sameSite: 'lax'
  });

  // Set session and redirect
  req.session.userId = user.id;
  res.redirect('/dashboard');
});

API Endpoints

Authentication

  • POST /auth/login — Email/password login
  • POST /auth/register — Email registration (sends verification email)
  • POST /auth/verify-email — Verify email with token
  • POST /auth/reset-password — Request password reset
  • GET /auth/callback/:provider — Social OAuth callback
  • POST /auth/token — Exchange authorization code or refresh token for a token pair
  • POST /auth/revoke — Revoke the refresh-token family for logout

Two-Factor Authentication

  • POST /2fa/setup — Initiate 2FA enrollment (returns QR code)
  • POST /2fa/verify — Verify TOTP code during setup or login
  • POST /2fa/reset — Email-based 2FA reset

Domain-Scoped APIs

  • GET /domain/users — List users for domain (requires domain hash token)
  • GET /domain/logs — Get login logs for domain
  • GET /domain/debug — Debug endpoints (superuser only)

Organisations, Teams & Groups (opt-in)

These endpoints require org_features.enabled: true in the config JWT.

User-Facing (require domain hash token + user access token):

  • POST /org/organisations — Create an organisation (auto-creates default team)
  • GET /org/organisations/:orgId — Get organisation details
  • PUT /org/organisations/:orgId — Update organisation
  • DELETE /org/organisations/:orgId — Delete organisation (owner only)
  • GET /org/organisations/:orgId/members — List members
  • POST /org/organisations/:orgId/members — Add member (by userId)
  • POST /org/organisations/:orgId/transfer-ownership — Transfer ownership
  • GET /org/organisations/:orgId/teams — List teams
  • POST /org/organisations/:orgId/teams — Create team
  • GET /org/organisations/:orgId/groups — List groups (read-only)
  • GET /org/me — Current user's org context

Internal API (require domain hash token only, no user token):

  • POST /internal/org/organisations/:orgId/groups — Create group
  • PUT /internal/org/organisations/:orgId/groups/:groupId — Update group
  • DELETE /internal/org/organisations/:orgId/groups/:groupId — Delete group
  • POST /internal/org/organisations/:orgId/groups/:groupId/members — Add group member
  • PUT /internal/org/organisations/:orgId/groups/:groupId/members/:userId — Toggle is_admin
  • DELETE /internal/org/organisations/:orgId/groups/:groupId/members/:userId — Remove group member
  • PUT /internal/org/organisations/:orgId/teams/:teamId/group — Assign/unassign team to group

See Section 24 of the brief for the full specification.

Health

  • GET /health — Health check endpoint

Public billing consumer protocol

UOA's display-ready BillingStatementV1 contract lives in the MIT-licensed @unlikeotherai/billing-statement-protocol workspace alongside the complete customer-action contract: hosted redirect, cancellation selection, preview and exact confirm_action, confirmation request/response, and the minimal error envelope. It exports public TypeScript types and exact runtime schemas, plus generated JSON Schema, OpenAPI 3.1, and synthetic conformance artifacts. It contains no private server imports, credentials, or production tenant data and can be packed or vendored by open-source consumers before registry publication.

The same artifacts are available without authentication at:

  • GET /schemas/billing-statement-v1.json
  • GET /schemas/billing-statement-v1.example.json
  • GET /schemas/billing-statement-v1.openapi.json
  • GET /schemas/billing-consumer-actions-v1.json
  • GET /schemas/billing-consumer-actions-v1.example.json
  • GET /schemas/billing-consumer-actions-v1.openapi.json

Backend feature authorization uses GET /apps/:appId/flags?domain=…&userId=…&teamId=… with the product's domain-hash credential. :appId is opaque UOA App.id, userId is the UOA token subject, and teamId is the exact active UOA team. Capability checks require explicit true; empty/missing/error results fail closed.

Contributing

See CLAUDE.md for contributor guidelines and Docs/brief.md for the complete specification.

Key Rules:

  • Read the brief before making changes
  • Never remove content from Docs/brief.md unless explicitly instructed
  • Follow architecture patterns in Docs/Auth/architecture-api.md and Docs/Auth/architecture-auth.md
  • No code file longer than 500 lines
  • Keep the codebase reusable and clean
  • Security first: no enumeration, no information leakage, all config verified

License

MIT — See LICENSE file for details.

Support

For issues and questions:


Built with: Node.js, TypeScript, Fastify, React, Tailwind CSS, PostgreSQL, Prisma

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages