← Back to all posts
10 min readCentrali Team

BYOT: Use Your Own Identity Provider with Centrali

Keep using Clerk, Auth0, or Okta for authentication while leveraging Centrali for fine-grained authorization. No user duplication, no token exchange—just pass your JWT.

ProductFeatureAnnouncementIntegration

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:

  1. 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.

  2. 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.

  3. 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:

json
{ "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_):

json
{ "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

typescript
// 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:

typescript
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:

json
{ "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:

json
{ "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:

typescript
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:

typescript
// 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:

typescript
// 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:

AttributeDescription
ext_*Claims extracted via your claim mappings
is_external_principalAlways true for external tokens
external_issuerThe JWT issuer URL
external_subjectThe 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.

  1. Read the BYOT Guide — Full documentation with policy examples
  2. Clerk Integration Guide — Step-by-step Clerk setup
  3. 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.

Building something with Centrali and want to share feedback about this feature?

Email feedback@centrali.io