YeboID Integration Guide
How to add YeboID authentication to any Yebo product.
Overview
Integrating YeboID means:
- Users sign in with their YeboID (phone + PIN)
- Your product receives a JWT token
- You validate the token locally (no API call)
- You link users to your product's data
Time to integrate: ~2-4 hours
Prerequisites
Before starting:
- [ ] Get
YEBOID_JWT_SECRETfrom GCP Secret Manager - [ ] Install
@yeboid/nodeSDK - [ ] Add
yeboid_user_idcolumn 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_idRaw 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/nodeStep 3: Configure Environment
Add to your .env:
env
YEBOID_JWT_SECRET=your-shared-secret-from-gcp
YEBOID_API_URL=https://api.yeboid.comFor 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 };Step 6: Link Users to YeboID
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
- Sign in with YeboID
- Check your database has a new user with
yeboid_user_id - 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'
}
});
});Cross-Product Links
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
- Check
YEBOID_JWT_SECRETmatches the one in YeboID - Check token hasn't expired (15 min lifetime)
- Check token format (should be
Bearer <token>)
Users not linking
- Check
yeboid_user_idcolumn exists - Check unique constraint on
yeboid_user_id - Check
loadUsermiddleware is applied
Token not refreshing
- Check refresh token is stored correctly
- Check refresh endpoint URL
- Check refresh token hasn't expired (30 days)
Checklist
- [ ] Added
yeboid_user_idcolumn - [ ] 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
- Docs: https://docs.yeboid.com
- Issues: GitHub issues on yeboid repo
- Slack: #yeboid-integration
Last updated: March 18, 2026