Skip to content

YeboID Integration Guide ​

How to add YeboID authentication to any Yebo product.


Overview ​

Integrating YeboID means:

  1. Users sign in with their YeboID (phone + PIN)
  2. Your product receives a JWT token
  3. You validate the token locally (no API call)
  4. You link users to your product's data

Time to integrate: ~2-4 hours


Prerequisites ​

Before starting:

  • [ ] Get YEBOID_JWT_SECRET from GCP Secret Manager
  • [ ] Install @yeboid/node SDK
  • [ ] Add yeboid_user_id column to your users table

Step 1: Database Migration ​

Add a column to link your users to YeboID.

Prisma ​

prisma
model User {
  id            String   @id @default(uuid())
  yeboidUserId  String?  @unique @map("yeboid_user_id")
  
  // Your existing fields
  shopName      String?
  // ...
}
bash
npx prisma migrate dev --name add_yeboid_user_id

Raw SQL ​

sql
ALTER TABLE users ADD COLUMN yeboid_user_id UUID UNIQUE;
CREATE INDEX idx_users_yeboid ON users(yeboid_user_id);

Step 2: Install SDK ​

bash
npm install @yeboid/node

Step 3: Configure Environment ​

Add to your .env:

env
YEBOID_JWT_SECRET=your-shared-secret-from-gcp
YEBOID_API_URL=https://api.yeboid.com

For Cloud Run, mount the secret:

yaml
# In cloudbuild.yaml or deploy script
--update-secrets="YEBOID_JWT_SECRET=yeboid-jwt-secret:latest"

Step 4: Initialize SDK ​

javascript
// src/config/yeboid.js
const { YeboID } = require('@yeboid/node');

const yeboid = new YeboID({
  jwtSecret: process.env.YEBOID_JWT_SECRET,
  apiUrl: process.env.YEBOID_API_URL // optional, for fetching profiles
});

module.exports = yeboid;

Step 5: Add Auth Middleware ​

javascript
// src/middleware/auth.js
const yeboid = require('../config/yeboid');

// Require authentication
const requireAuth = async (req, res, next) => {
  try {
    const token = req.headers.authorization?.replace('Bearer ', '');
    
    if (!token) {
      return res.status(401).json({
        success: false,
        error: { code: 'UNAUTHORIZED', message: 'Authentication required' }
      });
    }
    
    // Validate token locally (no API call!)
    const payload = yeboid.verifyToken(token);
    
    // Attach to request
    req.yeboUserId = payload.userId;
    req.yeboHandle = payload.handle;
    req.yeboVerified = payload.verified;
    
    next();
  } catch (error) {
    return res.status(401).json({
      success: false,
      error: { code: 'INVALID_TOKEN', message: 'Invalid or expired token' }
    });
  }
};

// Optional authentication (doesn't fail if no token)
const optionalAuth = async (req, res, next) => {
  try {
    const token = req.headers.authorization?.replace('Bearer ', '');
    
    if (token) {
      const payload = yeboid.verifyToken(token);
      req.yeboUserId = payload.userId;
      req.yeboHandle = payload.handle;
      req.yeboVerified = payload.verified;
    }
    
    next();
  } catch {
    // Invalid token, continue without auth
    next();
  }
};

module.exports = { requireAuth, optionalAuth };

When a user accesses your product, find or create their local profile:

javascript
// src/middleware/loadUser.js
const prisma = require('../config/prisma');

const loadUser = async (req, res, next) => {
  if (!req.yeboUserId) {
    return next();
  }
  
  // Find existing user
  let user = await prisma.user.findUnique({
    where: { yeboidUserId: req.yeboUserId }
  });
  
  // Create if first time
  if (!user) {
    user = await prisma.user.create({
      data: {
        yeboidUserId: req.yeboUserId,
        // Optionally fetch and store profile data
        // name: await yeboid.getUser(req.yeboUserId).then(u => u.name)
      }
    });
  }
  
  req.user = user;
  next();
};

module.exports = loadUser;

Step 7: Protect Routes ​

javascript
// src/routes/api.js
const express = require('express');
const { requireAuth, optionalAuth } = require('../middleware/auth');
const loadUser = require('../middleware/loadUser');

const router = express.Router();

// Public route
router.get('/products', async (req, res) => {
  const products = await getProducts();
  res.json({ success: true, data: products });
});

// Protected route
router.get('/orders', requireAuth, loadUser, async (req, res) => {
  const orders = await getOrders(req.user.id);
  res.json({ success: true, data: orders });
});

// Protected route requiring verification
router.post('/sell', requireAuth, loadUser, async (req, res) => {
  if (!req.yeboVerified) {
    return res.status(403).json({
      success: false,
      error: { code: 'KYC_REQUIRED', message: 'Verification required to sell' }
    });
  }
  
  // ... create listing
});

module.exports = router;

Step 8: Frontend Integration ​

Option A: Redirect to YeboID ​

javascript
// Redirect user to YeboID login
function login() {
  const returnUrl = encodeURIComponent(window.location.href);
  window.location.href = `https://id.yebo.com/login?return=${returnUrl}`;
}

// On return, extract token from URL
const params = new URLSearchParams(window.location.search);
const token = params.get('token');
if (token) {
  localStorage.setItem('yeboid_token', token);
  // Clean up URL
  window.history.replaceState({}, '', window.location.pathname);
}

Option B: Embedded YeboID (SDK) ​

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

function App() {
  return (
    <YeboIDProvider clientId="your-product-id">
      <Header />
      <Content />
    </YeboIDProvider>
  );
}

function Header() {
  const { user, isAuthenticated, logout } = useYeboID();
  
  if (!isAuthenticated) {
    return <LoginButton />;
  }
  
  return (
    <div>
      <span>@{user.handle}</span>
      <button onClick={logout}>Logout</button>
    </div>
  );
}

Step 9: Token Refresh (Frontend) ​

javascript
// src/services/api.js
import axios from 'axios';

const api = axios.create({
  baseURL: '/api'
});

// Add token to requests
api.interceptors.request.use((config) => {
  const token = localStorage.getItem('yeboid_access_token');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

// Handle token expiry
api.interceptors.response.use(
  (response) => response,
  async (error) => {
    if (error.response?.status === 401) {
      const refreshToken = localStorage.getItem('yeboid_refresh_token');
      
      if (refreshToken) {
        try {
          const res = await axios.post('https://api.yeboid.com/auth/refresh', {
            refresh_token: refreshToken
          });
          
          // Save new tokens
          localStorage.setItem('yeboid_access_token', res.data.data.access_token);
          localStorage.setItem('yeboid_refresh_token', res.data.data.refresh_token);
          
          // Retry original request
          error.config.headers.Authorization = `Bearer ${res.data.data.access_token}`;
          return api.request(error.config);
        } catch {
          // Refresh failed, logout
          localStorage.removeItem('yeboid_access_token');
          localStorage.removeItem('yeboid_refresh_token');
          window.location.href = '/login';
        }
      }
    }
    return Promise.reject(error);
  }
);

export default api;

Step 10: Test Integration ​

Test Token Validation ​

bash
# Get a test token from YeboID staging
curl -X POST https://api-staging.yeboid.com/auth/signin \
  -H "Content-Type: application/json" \
  -d '{"phone": "+26878000000", "pin": "1234"}'

# Use token with your API
curl http://localhost:3000/api/orders \
  -H "Authorization: Bearer <token>"

Test User Linking ​

  1. Sign in with YeboID
  2. Check your database has a new user with yeboid_user_id
  3. Sign in again, verify same user is found

Migration for Existing Users ​

If you have existing users, link them by phone:

javascript
// One-time migration script
const migrateUsers = async () => {
  // Get all users from YeboID
  const yeboUsers = await yeboid.listUsers(); // Admin API
  
  // Create phone -> yeboid_id map
  const phoneMap = new Map(
    yeboUsers.map(u => [u.phone, u.id])
  );
  
  // Update your users
  const yourUsers = await prisma.user.findMany({
    where: { yeboidUserId: null }
  });
  
  for (const user of yourUsers) {
    const yeboidId = phoneMap.get(user.phone);
    if (yeboidId) {
      await prisma.user.update({
        where: { id: user.id },
        data: { yeboidUserId: yeboidId }
      });
      console.log(`Linked ${user.phone} -> ${yeboidId}`);
    }
  }
};

Common Patterns ​

Check KYC Status ​

javascript
router.post('/high-value-action', requireAuth, loadUser, async (req, res) => {
  // Actions over $100 require verification
  if (req.body.amount > 100 && !req.yeboVerified) {
    return res.status(403).json({
      success: false,
      error: {
        code: 'KYC_REQUIRED',
        message: 'Identity verification required for transactions over $100',
        details: {
          verify_url: 'https://id.yebo.com/verify'
        }
      }
    });
  }
  
  // Proceed with action
});

Display User Info ​

javascript
router.get('/profile', requireAuth, async (req, res) => {
  // Fetch full profile from YeboID if needed
  const yeboProfile = await yeboid.getUser(req.yeboUserId);
  
  res.json({
    success: true,
    data: {
      handle: yeboProfile.handle,
      name: yeboProfile.name,
      avatar: yeboProfile.avatar_url,
      verified: yeboProfile.kyc_status === 'verified'
    }
  });
});
javascript
// Link to user's YeboJobs profile
const yeboJobsUrl = `https://yebojobs.com/@${req.yeboHandle}`;

// Link to user's YeboShops store
const yeboShopsUrl = `https://yeboshops.com/@${req.yeboHandle}`;

Troubleshooting ​

"Invalid token" errors ​

  1. Check YEBOID_JWT_SECRET matches the one in YeboID
  2. Check token hasn't expired (15 min lifetime)
  3. Check token format (should be Bearer <token>)

Users not linking ​

  1. Check yeboid_user_id column exists
  2. Check unique constraint on yeboid_user_id
  3. Check loadUser middleware is applied

Token not refreshing ​

  1. Check refresh token is stored correctly
  2. Check refresh endpoint URL
  3. Check refresh token hasn't expired (30 days)

Checklist ​

  • [ ] Added yeboid_user_id column
  • [ ] Installed @yeboid/node
  • [ ] Configured YEBOID_JWT_SECRET
  • [ ] Added auth middleware
  • [ ] Added user loading middleware
  • [ ] Protected relevant routes
  • [ ] Frontend token handling
  • [ ] Token refresh logic
  • [ ] Tested with staging
  • [ ] Migrated existing users (if any)
  • [ ] Deployed to production

Support ​


Last updated: March 18, 2026

One chat. Everything done.