One of the most requested features from our developer community has been the ability to use their existing identity providers with Centrali. Today, we're excited to announce BYOT (Bring Your Own Token)—a feature that lets you pass JWTs from Clerk, Auth0, Okta, or any OIDC-compliant provider directly to Centrali for authorization decisions.
The Problem We're Solving
If you're building an application, you've probably already chosen an identity provider. Maybe you're using Clerk for its beautiful authentication UI, or Auth0 for its enterprise features, or Okta because your company standardized on it.
Until now, integrating these with Centrali meant one of three awkward options:
-
Duplicate users — Create Centrali users for each of your IdP users. This means data sync, additional management, and the risk of things getting out of sync.
-
Single service account — Use one Centrali service account for all API calls. This works, but you lose the ability to have per-user authorization—everyone gets the same permissions.
-
Build your own authorization — Implement authorization logic in your application code. This is error-prone, hard to audit, and doesn't scale.
None of these options are great. You want to use your existing identity provider and get fine-grained, policy-based authorization. Now you can.
How BYOT Works
The concept is simple: pass your IdP's JWT to Centrali, and we'll use the claims in that token to make authorization decisions.
┌─────────────────────────────────────────────────────────────────┐
│ Your Application │
├─────────────────────────────────────────────────────────────────┤
│ User signs in via Clerk/Auth0/Okta │
│ Gets JWT: { sub: "user_123", plan: "premium", role: "admin" } │
└─────────────────────────────────────────────────────────────────┘
│
│ JWT Token
▼
┌─────────────────────────────────────────────────────────────────┐
│ CENTRALI │
├─────────────────────────────────────────────────────────────────┤
│ 1. Validate JWT signature via JWKS │
│ 2. Extract claims → ext_plan, ext_role │
│ 3. Evaluate policy: IF ext_plan == "premium" THEN Allow │
│ 4. Return Allow/Deny │
└─────────────────────────────────────────────────────────────────┘No user duplication. No token exchange. No sync problems.
Setting It Up
1. Configure Your IdP in Centrali
Go to Settings → External Auth Providers and add your IdP:
{
"name": "My Clerk Provider",
"providerType": "clerk",
"issuer": "https://clerk.your-domain.com",
"allowedAudiences": ["your-app-id"],
"claimMappings": [
{ "attribute": "plan", "jwtPath": "metadata.plan" },
{ "attribute": "role", "jwtPath": "org_role" }
]
}Centrali fetches the JWKS from your provider automatically and caches it for performance.
2. Create a Policy Using Claims
Write policies that reference the extracted claims (prefixed with ext_):
{
"name": "premium_access",
"specification": {
"rules": [{
"rule_id": "premium-allow",
"effect": "Allow",
"conditions": [
{ "function": "string_equal", "attribute": "ext_plan", "value": "premium" }
]
}],
"default": { "effect": "Deny" }
}
}3. Check Authorization from Your App
// In your Next.js API route
import { auth } from '@clerk/nextjs/server';
import { CentraliSDK } from '@centrali-io/centrali-sdk';
const centrali = new CentraliSDK({
baseUrl: 'https://api.centrali.io',
workspaceId: 'your-workspace',
});
export async function POST(request: Request) {
const { getToken } = await auth();
const token = await getToken({ template: 'centrali' });
const result = await centrali.checkAuthorization({
token,
resource: 'premium-features',
action: 'access',
});
if (!result.data.allowed) {
return Response.json({ error: 'Upgrade to premium' }, { status: 403 });
}
// User has access
return Response.json({ data: '...' });
}That's it. Your Clerk JWT is validated against Clerk's JWKS, the claims are extracted, and your policy is evaluated.
Passing Context for Dynamic Authorization
Real authorization decisions often depend on more than just who the user is—they depend on what they're trying to do.
The context parameter lets you pass request-specific data:
const result = await centrali.checkAuthorization({
token: clerkJWT,
resource: 'orders',
action: 'approve',
context: {
orderId: 'order-123',
orderAmount: 50000,
department: 'sales',
},
});Then write policies that combine user claims with context:
{
"name": "order_approval",
"specification": {
"rules": [
{
"rule_id": "manager-high-value",
"effect": "Allow",
"conditions": [
{ "function": "string_equal", "attribute": "ext_role", "value": "manager" },
{
"function": "integer_greater_than",
"attribute": "request_metadata",
"metadata_key": "orderAmount",
"value": 10000
}
]
},
{
"rule_id": "anyone-low-value",
"effect": "Allow",
"conditions": [
{
"function": "integer_less_equal",
"attribute": "request_metadata",
"metadata_key": "orderAmount",
"value": 10000
}
]
}
],
"default": { "effect": "Deny" }
}
}This policy allows:
- Managers to approve orders over $10,000
- Anyone to approve orders $10,000 or less
- Denies everything else
Using Clerk JWT Templates
For Clerk specifically, you'll want to create a JWT Template that includes the claims Centrali needs.
In Clerk Dashboard → Configure → JWT Templates, create a new template:
{
"org_id": "{{org.id}}",
"org_role": "{{org.role}}",
"metadata": {
"plan": "{{user.public_metadata.plan}}",
"department": "{{user.public_metadata.department}}"
}
}Then get the token with that template:
const token = await getToken({ template: 'centrali' });The resulting JWT will contain your custom claims alongside the standard OIDC claims.
Two Powerful Use Cases
1. External IdP for Centrali Resources
Use your IdP to authorize access to Centrali's data layer:
// Check if user can read records
const canRead = await centrali.checkAuthorization({
token: clerkJWT,
resource: 'customer-records',
action: 'read',
});
if (canRead.data.allowed) {
const records = await centrali.records.list('customers');
}2. Authorization-as-a-Service
Use Centrali purely for authorization decisions in your own application:
// Define your own resources in Centrali
// Use Centrali purely for the authorization decision
const canApprove = await centrali.checkAuthorization({
token: clerkJWT,
resource: 'expense-reports', // Your custom resource
action: 'approve',
context: {
amount: expenseReport.amount,
category: expenseReport.category,
},
});
if (canApprove.data.allowed) {
// Handle approval in YOUR system
await yourDatabase.approveExpense(expenseReport.id);
}This is AuthZ-as-a-Service—you bring the identity, we handle the authorization logic.
Security by Design
BYOT includes several security measures:
- JWKS validation: Tokens are validated against your provider's public keys
- Issuer verification: Only tokens from registered providers are accepted
- Audience validation: Tokens must include the expected audience claim
- Algorithm restrictions: Only RS256 is allowed (no symmetric algorithms, no
alg: none) - Resource restrictions: External principals cannot access admin resources like user management or billing
Available Attributes
When writing policies for external tokens, you have access to:
| Attribute | Description |
|---|---|
ext_* | Claims extracted via your claim mappings |
is_external_principal | Always true for external tokens |
external_issuer | The JWT issuer URL |
external_subject | The JWT subject claim |
request_metadata.* | Context values via metadata_key |
Plus all the standard attributes like action, current_time, day_of_week, and ip_address.
Why We Built This
We believe the best authorization system is one that works with your existing stack, not against it. You shouldn't have to choose between a great authentication experience (Clerk, Auth0, Okta) and fine-grained authorization (Centrali).
BYOT gives you both:
- Your IdP's strengths: SSO, MFA, social login, user management
- Centrali's strengths: Policy-based authorization, audit logging, resource permissions
No compromises.
Get Started
BYOT is available now in all Centrali workspaces.
- Read the BYOT Guide — Full documentation with policy examples
- Clerk Integration Guide — Step-by-step Clerk setup
- Try it in the Console — Set up your first External Auth Provider
We'd love to hear what you build with BYOT. Drop us a line at feedback@centrali.io.