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.