Custom Domains & Branding
Map custom domains to tenants, serve per-tenant login pages, and issue tenant-scoped JWKS endpoints.
Custom Domains & Branding
Phase 2. Custom domain mapping, per-tenant branding, and tenant-scoped JWKS endpoints ship Phase 2.
CoreSDK lets each tenant bring their own domain, branding, and JWKS endpoint — enabling full white-label deployments where tenants never see your primary domain.
Rust API — DomainRegistry and DomainMapping
DomainRegistry is the in-process store that maps hostnames to tenants. DomainMapping is the record for a single domain:
use coresdk_domains::{DomainRegistry, DomainMapping};
// The registry is created once and shared via Arc
let registry = DomainRegistry::new();
// Register a mapping
registry.insert(DomainMapping {
domain: "auth.acme.com".to_string(),
tenant_id: "acme".to_string(),
active: true,
});
// Resolve a tenant from an incoming Host header value
if let Some(mapping) = registry.resolve("auth.acme.com") {
println!("Tenant: {}", mapping.tenant_id);
}
// Per-tenant JWKS endpoint path
// Pattern: /.well-known/{tenant_id}/jwks.json
// On a custom domain the tenant ID is implicit from the domain:
// https://auth.acme.com/.well-known/jwks.json
let jwks_url = mapping.jwks_endpoint("https://auth.acme.com");
// → "https://auth.acme.com/.well-known/jwks.json"resolve() performs a hash-map lookup keyed by the exact Host header value — no DNS resolution, sub-domain wildcard matching, or port stripping is applied. Normalise the host header before calling resolve() if your service receives traffic on non-standard ports.
Mapping custom domains to tenants
Register a custom domain for a tenant. CoreSDK resolves the tenant from the incoming Host header automatically once the mapping is active.
use coresdk_engine::domains::{DomainCreate, TlsMode};
// Register the domain
sdk.domains().create(DomainCreate {
tenant_id: "acme".to_string(),
domain: "auth.acme.com".to_string(),
tls: TlsMode::AutoProvision, // CoreSDK handles ACME/Let's Encrypt
}).await?;
// Verify DNS is pointed correctly before activation
let status = sdk.domains().verify("auth.acme.com").await?;
println!("DNS verified: {}", status.dns_ok);
// Activate (starts serving traffic)
sdk.domains().activate("auth.acme.com").await?;# Register the domain
await sdk.domains.create({
"tenant_id": "acme",
"domain": "auth.acme.com",
"tls": "auto_provision", # CoreSDK handles ACME/Let's Encrypt
})
# Verify DNS
status = await sdk.domains.verify("auth.acme.com")
print(f"DNS verified: {status.dns_ok}")
# Activate
await sdk.domains.activate("auth.acme.com")// Register the domain
_, err := sdk.Domains().Create(ctx, coresdk.DomainCreate{
TenantID: "acme",
Domain: "auth.acme.com",
TLS: coresdk.TLSAutoProvision,
})
// Verify DNS
status, _ := sdk.Domains().Verify(ctx, "auth.acme.com")
fmt.Printf("DNS verified: %v\n", status.DNSOK)
// Activate
err = sdk.Domains().Activate(ctx, "auth.acme.com")// Register the domain
await sdk.domains.create({
tenantId: "acme",
domain: "auth.acme.com",
tls: "auto_provision", // CoreSDK handles ACME/Let's Encrypt
});
// Verify DNS
const status = await sdk.domains.verify("auth.acme.com");
console.log(`DNS verified: ${status.dnsOk}`);
// Activate
await sdk.domains.activate("auth.acme.com");The tenant's CNAME target is returned in the DomainCreate response as cname_target. Point auth.acme.com CNAME <cname_target> at your DNS provider.
Per-tenant login pages
Configure branding applied to the hosted login UI on a per-tenant basis: logo, colors, copy, and redirect URLs.
use coresdk_engine::branding::BrandingConfig;
sdk.branding().set("acme", BrandingConfig {
logo_url: "https://assets.acme.com/logo.svg".to_string(),
favicon_url: Some("https://assets.acme.com/favicon.ico".to_string()),
primary_color: "#0066FF".to_string(),
background_color: "#F5F7FA".to_string(),
company_name: "Acme Corp".to_string(),
support_email: Some("support@acme.com".to_string()),
terms_url: Some("https://acme.com/terms".to_string()),
privacy_url: Some("https://acme.com/privacy".to_string()),
// After login, redirect to the tenant's app
post_login_redirect: "https://app.acme.com/dashboard".to_string(),
}).await?;await sdk.branding.set("acme", {
"logo_url": "https://assets.acme.com/logo.svg",
"favicon_url": "https://assets.acme.com/favicon.ico",
"primary_color": "#0066FF",
"background_color": "#F5F7FA",
"company_name": "Acme Corp",
"support_email": "support@acme.com",
"terms_url": "https://acme.com/terms",
"privacy_url": "https://acme.com/privacy",
"post_login_redirect": "https://app.acme.com/dashboard",
})err := sdk.Branding().Set(ctx, "acme", coresdk.BrandingConfig{
LogoURL: "https://assets.acme.com/logo.svg",
FaviconURL: ptr("https://assets.acme.com/favicon.ico"),
PrimaryColor: "#0066FF",
BackgroundColor: "#F5F7FA",
CompanyName: "Acme Corp",
SupportEmail: ptr("support@acme.com"),
TermsURL: ptr("https://acme.com/terms"),
PrivacyURL: ptr("https://acme.com/privacy"),
PostLoginRedirect: "https://app.acme.com/dashboard",
})await sdk.branding.set("acme", {
logoUrl: "https://assets.acme.com/logo.svg",
faviconUrl: "https://assets.acme.com/favicon.ico",
primaryColor: "#0066FF",
backgroundColor: "#F5F7FA",
companyName: "Acme Corp",
supportEmail: "support@acme.com",
termsUrl: "https://acme.com/terms",
privacyUrl: "https://acme.com/privacy",
postLoginRedirect: "https://app.acme.com/dashboard",
});Custom JWKS endpoints per tenant
Each tenant can publish tokens signed with their own signing keys, served at their custom domain. This allows downstream services to validate tokens using a stable, tenant-specific JWKS URL.
use coresdk_engine::jwks::{SigningKeyCreate, KeyAlgorithm};
// Create a dedicated signing key for the tenant
let key = sdk.jwks().create_key("acme", SigningKeyCreate {
algorithm: KeyAlgorithm::ES256,
// Rotate automatically every 90 days
rotation_days: Some(90),
}).await?;
// The JWKS endpoint is now live at the tenant's custom domain:
// https://auth.acme.com/.well-known/jwks.json
// Issue a token signed with the tenant key
let token = sdk.tokens().issue_for_tenant("acme", claims).await?;
// Verify using the tenant-scoped JWKS
let verified = sdk.tokens()
.verify_with_tenant_jwks("acme", &token)
.await?;# Create a dedicated signing key
key = await sdk.jwks.create_key("acme", {
"algorithm": "ES256",
"rotation_days": 90,
})
# JWKS endpoint: https://auth.acme.com/.well-known/jwks.json
# Issue a token signed with the tenant key
token = await sdk.tokens.issue_for_tenant("acme", claims)
# Verify using the tenant JWKS
verified = await sdk.tokens.verify_with_tenant_jwks("acme", token)// Create a dedicated signing key
key, err := sdk.JWKS().CreateKey(ctx, "acme", coresdk.SigningKeyCreate{
Algorithm: coresdk.AlgorithmES256,
RotationDays: ptr(90),
})
// JWKS endpoint: https://auth.acme.com/.well-known/jwks.json
// Issue a token signed with the tenant key
token, err := sdk.Tokens().IssueForTenant(ctx, "acme", claims)
// Verify using the tenant JWKS
verified, err := sdk.Tokens().VerifyWithTenantJWKS(ctx, "acme", token)// Create a dedicated signing key
const key = await sdk.jwks.createKey("acme", {
algorithm: "ES256",
rotationDays: 90,
});
// JWKS endpoint: https://auth.acme.com/.well-known/jwks.json
// Issue a token signed with the tenant key
const token = await sdk.tokens.issueForTenant("acme", claims);
// Verify using the tenant JWKS
const verified = await sdk.tokens.verifyWithTenantJwks("acme", token);White-labeling: full domain isolation
For complete white-label deployments, combine a custom domain, branding, and a JWKS endpoint so the tenant's users never encounter your primary domain.
use coresdk_engine::whitelabel::WhitelabelConfig;
// Apply all white-label settings in one call
sdk.whitelabel().configure("acme", WhitelabelConfig {
domain: "auth.acme.com".to_string(),
branding: BrandingConfig {
company_name: "Acme Corp".to_string(),
logo_url: "https://assets.acme.com/logo.svg".to_string(),
primary_color: "#0066FF".to_string(),
post_login_redirect: "https://app.acme.com".to_string(),
..Default::default()
},
jwks: SigningKeyCreate {
algorithm: KeyAlgorithm::ES256,
rotation_days: Some(90),
},
// Suppress all CoreSDK references in emails and UI
hide_powered_by: true,
}).await?;await sdk.whitelabel.configure("acme", {
"domain": "auth.acme.com",
"branding": {
"company_name": "Acme Corp",
"logo_url": "https://assets.acme.com/logo.svg",
"primary_color": "#0066FF",
"post_login_redirect": "https://app.acme.com",
},
"jwks": {
"algorithm": "ES256",
"rotation_days": 90,
},
"hide_powered_by": True,
})err := sdk.Whitelabel().Configure(ctx, "acme", coresdk.WhitelabelConfig{
Domain: "auth.acme.com",
Branding: coresdk.BrandingConfig{
CompanyName: "Acme Corp",
LogoURL: "https://assets.acme.com/logo.svg",
PrimaryColor: "#0066FF",
PostLoginRedirect: "https://app.acme.com",
},
JWKS: coresdk.SigningKeyCreate{
Algorithm: coresdk.AlgorithmES256,
RotationDays: ptr(90),
},
HidePoweredBy: true,
})await sdk.whitelabel.configure("acme", {
domain: "auth.acme.com",
branding: {
companyName: "Acme Corp",
logoUrl: "https://assets.acme.com/logo.svg",
primaryColor: "#0066FF",
postLoginRedirect: "https://app.acme.com",
},
jwks: {
algorithm: "ES256",
rotationDays: 90,
},
hidePoweredBy: true,
});Next steps
- Tenant Provisioning — create tenants with plan metadata
- Quotas & Rate Limits — enforce per-tenant usage caps
- Tenant Isolation — policy namespaces and audit streams