This is a thought experiment: what if embedded services could just be plopped in your frontend with zero configuration while still being secure? This isn’t something I plan to implement, but I thought it was interesting and worth sharing.
Part 1: Why Self-Hosted Bootstrap Matters
What Char Does Today
Char validates tokens from your identity provider. It doesn’t initiate authentication. It receives tokens your app already obtained and verifies them using public keys. This is different from “Login with Google” flows. When Slack implements Google login, Slack acts as an OAuth Client. Slack needs its own Client ID registered with Google. Every vendor needs their own registration. Char acts as a Passive Validator. We don’t initiate auth. We don’t need our own Client ID with your IDP. We validate tokens that were already issued to your app: the tokens your users already have. The question Char needs answered: which IDP issued this token? Once we know that, validation is standard OIDC: fetch the IDP’s public keys, verify the signature, check the issuer and audience claims.Manual Configuration Today
You tell us which IDP to validate against by configuring it in our dashboard:- Log into Char dashboard
- Navigate to Settings → Integration
- Select your IDP type (Okta, Google, Auth0…)
- Copy your Client ID from your IDP
- Paste it into our form
- Copy your IDP domain
- Paste it into our form
- Add your application’s domain to our allowlist
- Click Save
Declarative Configuration
What if you declared your IDP configuration in a file you control? You host a JSON document at a well-known URL. When a request arrives from your domain, we fetch the file, learn which IDP to validate against, and proceed. No dashboard. No copy-paste. The configuration lives in your infrastructure, under your version control. This isn’t a new idea. Client ID Metadata Documents (CIMD) emerged from a similar frustration in the OAuth world. The MCP ecosystem faced a scaling problem: AI clients connecting to thousands of servers, each requiring registration. CIMD inverts the model. Instead of clients registering with servers, clients publish their own metadata at a URL they control. The URL is the identity. Domain ownership becomes the trust anchor. The MCP spec adopted CIMD in November 2025. Auth0, Stytch, WorkOS, and others have published guidance and product support for CIMD in the MCP context (availability varies by provider).This isn’t OIDC Federation. OIDC Federation solves a different problem: “Should this IDP issue tokens to an unknown Relying Party?” That requires trust chains, signed Entity Statements, and complex machinery, because the IDP is making a consequential decision.We’re solving a narrower problem: “Which IDP should I validate this token against?” We don’t ask the IDP for permission. We just fetch its public keys (which are public) and verify signatures. Domain ownership is sufficient trust for that question.
CIMD Extension
Extend CIMD with IDP configuration. A customer hosts a file at/.well-known/oauth-client that includes:
crm.acme.com, we fetch this file, learn that tokens should be validated against the declared issuer with that expected audience, and proceed. The customer declared their truth in a file they control. We read it. No dashboard required.
This isn’t just about reducing clicks. It’s about putting configuration where it belongs: with the customer, in their infrastructure, under their version control.
The Trust Model
The elegance of this approach is that it doesn’t require new trust mechanisms. It composes existing ones: Domain ownership is already how the web establishes identity. If you can host files atcrm.acme.com, you control that domain. HTTPS and DNS provide that guarantee. CIMD simply uses it.
IDP tokens are already how enterprises establish user identity. The customer’s IDP signs tokens with keys only it possesses. We validate signatures against published public keys. This is federated authentication, and we already do it.
Audience claims already prevent token confusion. A token for one application can’t be used for another. We already validate this.
CIMD-based discovery just connects these existing mechanisms. The customer’s domain proves they’re legitimate. Their CIMD declares their IDP. Their IDP’s tokens prove user identity. No new cryptography. No new trust assumptions. Just composition.
What This Enables
Instant trials. Embed the agent, host a CIMD, it works. No account required. Conversion happens when customers hit limits, not before they’ve seen why the product matters. Configuration-as-code. The CIMD is a file in the repository. It goes through code review. It deploys through CI/CD. Staging and production differ by environment, not by dashboard. Self-service onboarding. If you can host a valid CIMD, you’re onboarded. No waiting for us to approve anything. Single source of truth. Configuration lives in one place: yours. We don’t have a stale copy that disagrees with your IDP.Broader Applicability
This pattern isn’t specific to Char. Any embedded SaaS that needs to verify user identity faces the same problem. Today, services like Intercom and Zendesk use HMAC shared secrets: the customer generates a secret, passes user data server-side, and the service validates the signature. This works, but it requires server-side integration and secret management. Every vendor has their own approach. Token Issuer Metadata eliminates this. If your app already authenticates users via OIDC (most do), you’re already issuing tokens. Third-party services embedded in your app can validate those tokens directly. One CIMD file, hosted once, works for any service that implements the spec. The network effect: once a customer hosts a CIMD withtoken_issuer, every third-party validator benefits. The first vendor to push adoption creates value for the whole ecosystem.
The Tradeoffs
This model has genuine limitations worth acknowledging: GitOps vs. ClickOps. This model shifts configuration from “ClickOps” (admins pasting values into dashboards) to “GitOps” (engineers committing config to code). For engineering-led organizations, this is a feature: version control, code review, reproducible environments. For non-technical teams who can’t easily host a JSON file, it’s a barrier. The hybrid approach (supporting both CIMD and dashboard) bridges this gap. No pre-flight validation. With dashboard configuration, customers can click “Test Connection” before users arrive. With CIMD discovery, the first real user is the test. Errors surface in production. Achar cimd validate CLI tool or web-based validator could help customers catch errors before deploying.
Bootstrap, not runtime. CIMD fetch happens once, when we first see requests from a new origin. After that, the configuration lives in our infrastructure. This means CIMD availability isn’t a runtime concern: if your app loads, we can reach your CIMD. (Char is embedded in your page. If your CDN is down, the page doesn’t load, and we never receive a request.) What does matter is config drift. If you rotate your Okta client ID but forget to update the CIMD file, tokens will fail validation.
Domain compromise scope. If an attacker gains control of a customer’s domain, they can modify the CIMD. This is true of any domain-based trust model (it’s the same threat model as HTTPS itself), but worth noting explicitly.
Standardization. The token_issuer extension doesn’t exist as a standard yet. This document proposes one. Adoption depends on whether other vendors see value in a shared approach.
Part 2: Technical Specification
The following sketches what a specification for CIMD-based IDP discovery might look like.Extended CIMD Document
A standard CIMD document contains OAuth client metadata. We extend it with identity provider configuration:token_issuer object declares how to validate tokens from users of this application. Validators fetch {issuer}/.well-known/openid-configuration to discover the JWKS endpoint, then verify that the token’s aud claim matches expected_audience.
On standardization: The
token_issuer field is a proposed extension to CIMD. While CIMD itself is an IETF draft with growing adoption, there is no standard for declaring IDP configuration inside a client metadata document. This proposal suggests one. Char is an early implementer, but the pattern is designed to be vendor-neutral and adoptable by any service that validates tokens passively.Token Issuer Fields
| Field | Required | Description |
|---|---|---|
issuer | Yes | Full issuer URL. Must match the token’s iss claim. JWKS discovered via {issuer}/.well-known/openid-configuration. |
expected_audience | Yes | Expected value of the token’s aud claim. This is typically the OAuth client ID of the customer’s application. |
type | No | Optional hint: google, okta, azure, auth0. Used for IDP-specific validation quirks (e.g., Google accepts both https://accounts.google.com and accounts.google.com as issuer). |
Optional Admin Configuration
To enable dashboard access for auto-provisioned organizations:Discovery Flow
Auto-Provisioning Behavior
When Char receives a request from an unknown origin with a valid-looking token: Discovery: Fetch{origin}/.well-known/oauth-client. If not found or invalid, reject the request (fall back to requiring dashboard configuration).
Validation: Parse the token_issuer extension. Validate the token against the declared IDP: fetch JWKS, verify signature, check issuer and audience claims.
Provisioning: If validation succeeds, create an organization record with:
- The origin as the primary allowed domain
- IDP configuration from the CIMD
- Free tier defaults
- The authenticated user as the first end-user
Dashboard Access Flow
For customers to access the dashboard after auto-provisioning: Matching logic:- For Google: match by
hd(hosted domain) claim - For Okta/Auth0: match by issuer URL
- For Azure: match by
tid(tenant ID) claim - Fallback: match by email domain
Security Considerations
Core Validations
CIMD URL validation: Theclient_id field in the CIMD must exactly match the URL it was fetched from. This prevents an attacker from hosting a CIMD that claims to be a different domain.
HTTPS required: CIMD must be served over HTTPS. HTTP URLs are rejected.
Issuer validation: The token’s iss claim must match the expected issuer for the declared IDP type. A CIMD declaring type: google cannot be used to validate tokens from Okta.
Audience validation: The token’s aud claim must match the expected_audience declared in the CIMD. This prevents tokens intended for other applications from being accepted.
Origin binding (preventing token replay): A natural concern: “If you accept tokens meant for other apps, can’t I steal a token from App A and use it on App B?” No, because the CIMD is fetched from the request’s origin:
- Attacker steals token from
app-a.com(whereaud: "client-id-A") - Attacker sends token to Char from
app-b.com - Char fetches CIMD from
app-b.com - CIMD declares
expected_audience: "client-id-B" - Token’s
auddoesn’t match → Rejected
On Origin headers: Origin can be spoofed by non-browser clients. Origin binding is a routing hint, not a cryptographic proof. The actual security boundary is audience validation: the token’s
aud claim must match the CIMD’s declared expected_audience. If an attacker could forge a valid token with the correct audience, they wouldn’t need to spoof Origin in the first place.SSRF Hardening (Normative)
Fetching customer-hosted metadata creates SSRF risk. Implementations MUST:- Reject non-HTTPS URLs. No
http://,file://, or other schemes. - Block private/loopback addresses. Resolve DNS before connecting and reject private ranges (10.x, 172.16-31.x, 192.168.x, 127.x, ::1, link-local). Re-check after any redirects.
- Cap response size. 5 KB maximum for metadata documents.
- Use aggressive timeouts. 5 seconds connect, 10 seconds total.
- Rate limit per origin. Prevent abuse from repeated fetch attempts.
Domain Compromise Scope
A natural concern: “What if an attacker gains control of a customer’s domain?” The blast radius is more contained than it might appear. Even with full domain control, an attacker must still present tokens from a valid IDP. They could modify the CIMD to point to an IDP they control, but tokens from that IDP would only grant access to the organization associated with that domain. They cannot use a compromised domain to attack other organizations. Each org is isolated by its origin. The attacker is essentially attacking themselves. They control the domain, they control the CIMD, they control the IDP, and they get access to… an organization scoped to their own domain. This is the same trust model as HTTPS itself: domain ownership is the root of trust.Token Lifecycle as Defense
The short-lived nature of OAuth tokens provides defense-in-depth:- ID tokens expire quickly. Best practice is 5-60 minutes. Even if a CIMD is compromised, existing user sessions expire naturally within this window.
- Refresh tokens rotate. Modern IDPs issue new refresh tokens with each use. A stolen refresh token becomes invalid after one use. Many IDPs also detect reuse and revoke the entire token family.
- CIMD changes invalidate sessions. If a domain changes hands and the new owner modifies the CIMD, existing tokens fail validation immediately. The issuer or audience no longer matches. There’s no persistent access that survives a CIMD change.
Edge Cases Worth Noting
Subdomain takeover: If a customer has dangling DNS records pointing to decommissioned services (e.g., old Heroku or S3 deployments), an attacker could claim that subdomain and host a malicious CIMD. This is a real attack vector, but it’s a general web security issue, not specific to CIMD. The mitigation is the same: customers should audit their DNS records and remove dangling entries. Domain expiration: If a customer’s domain expires and someone else registers it, the new owner could claim the auto-provisioned organization. This is concerning for abandoned deployments. Mitigation options include:- Periodic re-validation of domain ownership
- Alerting on CIMD changes (especially IDP changes)
- Requiring explicit confirmation for significant configuration changes
admin_contact with an email, an attacker who knows that email and can obtain a valid token for it could beat the legitimate admin to claiming the org. This is a narrow window (only during initial provisioning) and requires the attacker to have legitimate IDP access. Low risk, but worth documenting.
What We’re Trusting
To summarize the trust model:| We Trust | Because |
|---|---|
| Domain ownership proves legitimacy | HTTPS + DNS (same as all web security) |
| CIMD content is authoritative | Only the domain owner can host it |
| IDP tokens prove user identity | Cryptographic signatures we verify |
| Token audience prevents confusion | Standard OIDC claim validation |
| Short token lifetimes limit exposure | Industry best practice, IDP-enforced |
Relationship to OpenID Connect
This proposal occupies similar territory to existing OIDC specs, but it’s solving a narrower problem, which is why it can be simpler.What OIDC Federation Solves
OpenID Connect Federation 1.0 addresses a broad challenge: how can an Identity Provider accept authentication requests from Relying Parties it has never seen before? The answer is sophisticated. Trust anchors issue signed statements about entities. Intermediate authorities form chains. RPs publish Entity Statements (signed JWTs) containing their metadata. When an RP initiates auth, the IDP walks the trust chain, validates signatures, applies federation policies, and decides whether to proceed. This machinery exists because OIDC Federation enables the full authentication flow: authorization requests, token issuance, consent, scopes. The IDP is actively participating. It needs strong guarantees before issuing tokens to an unknown party.What We’re Actually Solving
Our problem is narrower: how can a third party know which IDP to validate tokens against, without pre-registration? We’re not participating in the auth flow. The customer’s IDP already issued the token. The user already authenticated. We just need to validate what we received. We’re asking a simpler question: “which IDP does this domain use?” The IDP doesn’t know we exist. We don’t ask it for permission. We just fetch its public keys (which are public) and verify signatures. This is standard OIDC token validation. The only question is which IDP to validate against.Why Our Approach Is Simpler
The simplicity isn’t a shortcut. It’s appropriate for the scope:| OIDC Federation | This Proposal | |
|---|---|---|
| Question answered | ”Should I issue tokens to this RP?" | "Which IDP issued this token?” |
| Who’s deciding | The IDP (active participant) | Us (passive validator) |
| What happens next | Full auth flow, token issuance | Token validation only |
| Trust requirements | IDP must trust RP before issuing tokens | We just need to know which IDP to check |
| Failure mode | Auth blocked | Validation fails, user re-authenticates |
What We Build On
To be precise about lineage: We build on CIMD. The core pattern (publish metadata at your domain, domain ownership = trust) comes directly from OAuth Client ID Metadata Documents. We’re extending CIMD with IDP configuration fields. We use OIDC. Token validation is standard OIDC. We fetch the IDP’s JWKS via/.well-known/openid-configuration, verify signatures, check issuer and audience claims. Nothing novel here.
We occupy similar space as OIDC Federation. Both enable dynamic discovery without pre-registration. But we’re not building on Federation’s trust chain model because we don’t need it.
Summary
OIDC Federation is the right tool when IDPs need to trust unknown RPs before issuing tokens. For token validation by third parties, that machinery is overkill. Domain ownership is sufficient trust. The token itself carries cryptographic proof of the IDP’s involvement. We’re not asking the IDP to do anything, just reading its public keys. This proposal achieves dashboard-free SSO bootstrap for embedded agents by recognizing that token validation is a narrower problem than token issuance.Current Status
This is exploratory work. CIMD itself is an IETF draft with growing adoption, but thetoken_issuer extension described here doesn’t exist as a standard.
I’m not actively building this, just documenting an idea that seemed worth sharing. If it resonates, or if you see problems with the approach, I’d be curious to hear.
Further Reading
Federated Authentication
How Char validates tokens from your IDP
Identity Providers
Current (dashboard-based) IDP configuration

