Skip to content

Identity Architecture

How authentication works across Yebo.


Core Principle

One identity, everywhere.

When you create a YeboID:

  • You get one @handle
  • You authenticate once
  • All products recognize you

Architecture

┌─────────────────────────────────────────────────────────────┐
│                        USER                                  │
│            Phone: +254712345678 • @laslie                   │
└─────────────────────────────────────────────────────────────┘

                    Authenticates

┌─────────────────────────────────────────────────────────────┐
│                       YEBOID                                 │
│                                                              │
│   • Validates phone + PIN                                   │
│   • Issues JWT tokens (access + refresh)                    │
│   • Manages profile, handle, KYC                            │
│                                                              │
└─────────────────────────────────────────────────────────────┘

                    Issues JWT

         ┌─────────────────┼─────────────────┐
         │                 │                 │
         ▼                 ▼                 ▼
   ┌───────────┐     ┌───────────┐     ┌───────────┐
   │YeboShops  │     │YeboJobs   │     │YeboLearn  │
   │           │     │           │     │           │
   │ Validates │     │ Validates │     │ Validates │
   │ JWT local │     │ JWT local │     │ JWT local │
   └───────────┘     └───────────┘     └───────────┘

Token System

Access Token

  • Type: JWT
  • Lifetime: 15 minutes
  • Content:
json
{
  "userId": "uuid",
  "handle": "laslie",
  "phone": "+254712345678",
  "verified": true,
  "iat": 1710770000,
  "exp": 1710770900
}
  • Validation: Local (no API call)

Refresh Token

  • Type: Opaque string
  • Lifetime: 30 days
  • Storage: Hashed in database
  • Rotation: New one issued on each use

Shared Secret

All Yebo products share YEBOID_JWT_SECRET:

GCP Secret Manager (org level)

         └── yeboid-jwt-secret

     ┌──────────────┼──────────────┐
     │              │              │
     ▼              ▼              ▼
YeboShops      YeboJobs      YeboLearn
 (mounted)     (mounted)     (mounted)

This allows local validation:

javascript
// In any Yebo product
const jwt = require('jsonwebtoken');

function validateToken(token) {
  return jwt.verify(token, process.env.YEBOID_JWT_SECRET);
}

// Middleware
function authMiddleware(req, res, next) {
  const token = req.headers.authorization?.split(' ')[1];
  
  try {
    const payload = validateToken(token);
    req.userId = payload.userId;
    req.handle = payload.handle;
    req.verified = payload.verified;
    next();
  } catch {
    res.status(401).json({ error: 'Invalid token' });
  }
}

User Linking

Each product has its own user table, linked to YeboID:

sql
-- In YeboShops
CREATE TABLE users (
  id UUID PRIMARY KEY,
  yeboid_user_id UUID UNIQUE NOT NULL,
  -- Product-specific fields
  seller_rating DECIMAL,
  total_sales INTEGER DEFAULT 0
);

-- In YeboJobs
CREATE TABLE users (
  id UUID PRIMARY KEY,
  yeboid_user_id UUID UNIQUE NOT NULL,
  -- Product-specific fields
  cv_url TEXT,
  skills JSONB
);

First-Time Access

javascript
async function getOrCreateUser(yeboidUserId) {
  let user = await db.users.findByYeboId(yeboidUserId);
  
  if (!user) {
    // First time on this product
    user = await db.users.create({ yeboidUserId });
  }
  
  return user;
}

Authentication Flows

Login

User                    YeboID                  Product
  │                        │                       │
  │──── Phone + PIN ──────►│                       │
  │                        │                       │
  │◄─── Access Token ──────│                       │
  │◄─── Refresh Token ─────│                       │
  │                        │                       │
  │──── Request + Token ──────────────────────────►│
  │                        │                       │
  │                        │    (validates locally)│
  │                        │                       │
  │◄─────────────────── Response ──────────────────│

Token Refresh

User                    YeboID
  │                        │
  │── Refresh Token ──────►│
  │                        │
  │◄── New Access Token ───│
  │◄── New Refresh Token ──│ (old one invalidated)

Cross-Product

User authenticated with YeboShops

  │ Same token

YeboJobs validates same JWT

  │ User recognized

Continue without re-login

SDK Usage

Node.js SDK

javascript
const { YeboIDAuth } = require('@yeboid/node');

const auth = new YeboIDAuth({
  secret: process.env.YEBOID_JWT_SECRET
});

// Middleware
app.use(auth.middleware());

// In route
app.get('/profile', auth.required, (req, res) => {
  // req.yeboUser available
  res.json({
    userId: req.yeboUser.userId,
    handle: req.yeboUser.handle
  });
});

React SDK

jsx
import { YeboIDProvider, useYeboID } from '@yeboid/react';

function App() {
  return (
    <YeboIDProvider clientId="yeboshops">
      <MyApp />
    </YeboIDProvider>
  );
}

function MyApp() {
  const { user, isAuthenticated, login, logout } = useYeboID();
  
  if (!isAuthenticated) {
    return <button onClick={login}>Login with YeboID</button>;
  }
  
  return <div>Welcome, @{user.handle}!</div>;
}

Security Considerations

Token Security

  • Short-lived access tokens (15 min)
  • Refresh token rotation
  • HTTPS only

PIN Security

  • bcrypt hashing (cost 12)
  • Rate limiting (5 attempts / 15 min)
  • Account lockout

Cross-Origin

  • CORS configured per product
  • Same-site cookies where applicable

Migration

For existing products with their own auth:

  1. Add yeboid_user_id column
  2. Match existing users by phone
  3. Update auth middleware
  4. Deprecate old login

See Integration Guide for details.

One chat. Everything done.