Skip to main content
CoreSDK
Multi-Tenancy

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

On this page