Production-grade team management UI + webhook-driven SCIM provisioning for SSOJet
SSOJet SSOKit is an open-source, batteries-included solution for building team management experiences powered by SSOJet APIs. It features drop-in React widgets, headless hooks, and automatic SCIM 2.0 provisioning triggered by webhooks.
- Drop-in TeamManager widget with members, invites, roles, and audit logs
- Fully themed with 4 presets (light, dark, minimal, enterprise) + custom tokens
- Headless hooks for building custom UIs
- Type-safe with full TypeScript support
- Signature verification for secure webhook handling
- Automatic SCIM sync from team events (member added/removed/role changed)
- Full SCIM 2.0 client for Users and Groups
- Customizable mapping strategies
- Zero hardcoded secrets - everything from env vars
- Client-safe config injection
- Helpful error messages for missing configuration
| Package | Description | Version |
|---|---|---|
@ssojet/ssokit-core |
Types, schemas, errors, config | |
@ssojet/ssokit-react |
React hooks and providers | |
@ssojet/ssokit-team |
TeamManager drop-in widget | |
@ssojet/ssokit-next |
Next.js route handlers | |
@ssojet/ssokit-webhooks |
Webhook + SCIM provisioning | |
@ssojet/ssokit-css |
Theming + Tailwind preset |
pnpm add @ssojet/ssokit-react @ssojet/ssokit-next @ssojet/ssokit-webhooks @ssojet/ssokit-css# .env.local
# SSOJet API (server-side)
DEFAULT_SSOJET_API_URL=https://api.ssojet.com
DEFAULT_SSOJET_CLIENT_ID=your-client-id
DEFAULT_SSOJET_CLIENT_SECRET=your-client-secret
DEFAULT_SSOJET_AUTHORITY=https://api.ssojet.com
# Webhook verification
SSOJET_WEBHOOK_SECRET=whsec_xxx
# SCIM downstream
SCIM_BASE_URL=https://scim.target.com/scim/v2
SCIM_TOKEN=scim_bearer_xxx
# Optional: Client-safe config
NEXT_PUBLIC_DEFAULT_SSOJET_CLIENT_ID=your-client-id
NEXT_PUBLIC_DEFAULT_SSOJET_AUTHORITY=https://api.ssojet.com// app/api/ssokit/orgs/[orgId]/route.ts
import { createRouteHandlers } from '@ssojet/ssokit-next/routes';
const handlers = createRouteHandlers();
export const GET = handlers.getOrganization;
export const PATCH = handlers.updateOrganization;// app/api/ssokit/orgs/[orgId]/members/route.ts
const handlers = createRouteHandlers();
export const GET = handlers.listMembers;
export const POST = handlers.addMember;(See route setup guide for all routes)
// app/api/ssokit/webhooks/route.ts
import { NextRequest, NextResponse } from 'next/server';
import {
verifySignature,
parseEvent,
createScimClient,
handleEventToSCIM,
} from '@ssojet/ssokit-webhooks';
import { readWebhookServerConfig, readScimServerConfig } from '@ssojet/ssokit-core/config';
export const runtime = 'nodejs';
export async function POST(req: NextRequest) {
const rawBody = Buffer.from(await req.arrayBuffer());
const signature = req.headers.get('ssojet-signature') || '';
try {
// 1. Verify signature
const webhookConfig = readWebhookServerConfig();
verifySignature({ rawBody, header: signature, secret: webhookConfig.secret });
// 2. Parse event
const event = parseEvent(rawBody.toString('utf8'));
// 3. Handle SCIM provisioning
const scimConfig = readScimServerConfig();
const scim = createScimClient(scimConfig);
await handleEventToSCIM({
event,
scimClient: scim,
orgIdScopeStrategy: (orgId, role) => `${orgId}:${role}s`,
});
return NextResponse.json({ received: true });
} catch (err: any) {
console.error('Webhook error:', err);
return NextResponse.json({ error: err.message }, { status: 400 });
}
}import { SSOJetClient } from '@ssojet/ssokit-next';
import { TeamManager } from '@ssojet/ssokit-team';
import '@ssojet/ssokit-css';
function TeamPage() {
const client = new SSOJetClient(accessToken);
return (
<TeamManager
organizationId="org_123"
client={client}
currentUserId="user_456"
currentUserEmail="user@example.com"
showAuditLog
/>
);
}βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Your Next.js App β
β β
β ββββββββββββββββββββ βββββββββββββββββββββββββββ β
β β TeamManager UI ββββββββββΆβ SSOJetClient β β
β β (@ssokit-team) β β (@ssokit-next) β β
β ββββββββββββββββββββ βββββββββββββββββββββββββββ β
β β β
β βΌ β
β βββββββββββββββββββββββββββ β
β β SSOJet API Client β β
β β (CLIENT_ID/SECRET) β β
β βββββββββββββββββββββββββββ β
β β β
ββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββ
β SSOJet API (Cloud) β
β api.ssojet.com/v1/... β
βββββββββββββββββββββββββββ
β
β Webhook Events
βΌ
βββββββββββββββββββββββββββ
β /api/v1/webhooks β
β (@ssokit-webhooks) β
βββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββ
β SCIM 2.0 Endpoint β
β (Your IdP/Directory) β
βββββββββββββββββββββββββββ
Flow:
- UI β SSOJet Client β SSOJet API: TeamManager uses SSOJetClient directly to communicate with SSOJet APIs
- SSOJet β Webhook β SCIM: Team events trigger webhooks, verified and converted to SCIM operations
SSOKit supports multiple theming approaches:
import '@ssojet/ssokit-css/presets/dark.css';import '@ssojet/ssokit-css/tokens.css';.sk-root {
--sk-primary: #2563eb;
--sk-radius: 12px;
}// tailwind.config.js
module.exports = {
presets: [require('@ssojet/ssokit-css/tailwind/preset.cjs')],
};- Server-side API key never exposed to client
- HMAC signature verification for webhooks
- Timing-safe comparisons prevent timing attacks
- Type-safe validation with Zod schemas
# Install dependencies
pnpm install
# Build all packages
pnpm -r build
# Run tests
pnpm test
# Run example app
cd examples/next-app
pnpm dev- Core types and schemas
- Webhook verification + SCIM client
- SSOJet API client
- Theming system with CSS presets
- TeamManager widget with full functionality
- Example Next.js application
- E2E tests
- Storybook components
- SDK documentation site
We welcome contributions! Please see CONTRIBUTING.md for guidelines.
MIT Β© SSOJet Team
Built with β€οΈ by the SSOJet team