Branding
Docs version v1 (main)Nauthera is pre-1.0, in a design-partner programUpdated
A Branding resource themes the hosted sign-in pages for a
single OAuth client: the product name, logo, favicon, links, layout and a full
design-system theme. The operator serializes spec.branding to
JSON and writes it to the server's gRPC AdminService (UpsertBranding). The server
checks it, stores it and serves it on the sign-in pages for that client, laid over the
organization's default branding field by field.
| Property | Value |
|---|---|
| API group / version | nauthera.io/v1alpha1 |
| Kind | Branding |
| Plural / short name | brandings / brand |
| Scope | Namespaced |
| Definition | deploy/ in nauthera-server |
Example
apiVersion: nauthera.io/v1alpha1
kind: Branding
metadata:
name: example-webapp-branding
namespace: nauthera
spec:
clientId: example-webapp
branding:
productName: Acme
logoUrl: https://acme.example/logo.svg
faviconUrl: https://acme.example/favicon.svg
mode: system
supportUrl: https://acme.example/support
fonts:
- https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap
theme:
color:
accent: "#7B61FF"
neutralStyle: cool
typography:
scale:
base: 14
ratio: 1.2
body:
family: Inter
fallbacks: "-apple-system, sans-serif"
radius:
base: 8
multiplier: 1kubectl get brandings shows the client ID, the phase and the Ready condition's
status and reason; -o wide adds the message.
The operator's identity needs the brandings operation, and the organization and
namespace, in its server. entry. The Helm chart's default entry
grants it in the main organization from every namespace.
Gallery
Each tab is one of the example brandings in nauthera-server's sign-in Storybook
(web/), shown on the sign-in screen in light and dark mode
with the Branding resource that produces it. The pictures and the documents are captured
from the same build, so the manifest is exactly what the picture wears. Inline hero images
are shortened here. For a step-by-step walk through designing a theme in the Astryx
playground and applying it, see Customize the sign-in UI.


apiVersion: nauthera.io/v1alpha1
kind: Branding
metadata:
name: neutral-branding
namespace: nauthera
spec:
clientId: neutral-web
branding:
productName: Nauthera


apiVersion: nauthera.io/v1alpha1
kind: Branding
metadata:
name: acme-branding
namespace: nauthera
spec:
clientId: acme-web
branding:
productName: Acme Corp
supportUrl: "https://example.com/support"
termsUrl: "https://example.com/terms"
privacyUrl: "https://example.com/privacy"
theme:
color:
accent: "#0064E0"
neutralStyle: cool
radius:
base: 10
multiplier: 1


apiVersion: nauthera.io/v1alpha1
kind: Branding
metadata:
name: sunset-branding
namespace: nauthera
spec:
clientId: sunset-web
branding:
productName: Sunset Labs
theme:
color:
accent: "#E4572E"
neutralStyle: warm
radius:
base: 16
multiplier: 1


apiVersion: nauthera.io/v1alpha1
kind: Branding
metadata:
name: evergreen-branding
namespace: nauthera
spec:
clientId: evergreen-web
branding:
productName: Evergreen
theme:
color:
accent: "#2E7D32"
neutralStyle: neutral
contrast: high
radius:
base: 4
multiplier: 1


apiVersion: nauthera.io/v1alpha1
kind: Branding
metadata:
name: violet-branding
namespace: nauthera
spec:
clientId: violet-web
branding:
productName: Violet
fonts: ["https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"]
theme:
color:
accent: "#7B61FF"
neutralStyle: cool
typography:
scale:
base: 14
ratio: 1.2
body:
family: Inter
fallbacks: "-apple-system, sans-serif"
radius:
base: 6
multiplier: 1
motion:
fast: 175
medium: 410
ratio: 0.75
tokens:
"--color-accent": ["#7B61FF", "#9B85FF"]
components:
button:
"variant:primary":
color: white


apiVersion: nauthera.io/v1alpha1
kind: Branding
metadata:
name: platform-branding
namespace: nauthera
spec:
clientId: platform-web
branding:
productName: nauthera
companyName: Acme Corp
appName: Portal
layout: split
fonts:
- "https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap"
hero:
heading: Sign-in, defined as code.
subheading: Identity defined as code. Versioned in Git, applied by your operator, reviewed like everything else in your cluster.
imageUrl: "data:image/svg+xml;… (inline image)"
side: left
theme:
color:
accent: "#1a7a4a"
neutralStyle: cool
radius:
base: 3
multiplier: 1
typography:
body:
family: JetBrains Mono
fallbacks: monospace
heading:
family: JetBrains Mono
fallbacks: monospace


apiVersion: nauthera.io/v1alpha1
kind: Branding
metadata:
name: aurora-branding
namespace: nauthera
spec:
clientId: aurora-web
branding:
productName: Aurora
companyName: Aurora Labs
appName: Aurora Cloud
layout: split
hero:
eyebrow: Welcome back
heading: Ship faster, sign in once.
subheading: "Aurora Cloud gives your team a single, secure front door to every service — no passwords to remember, no VPN to fight."
imageUrl: "data:image/svg+xml;… (inline image)"
side: right
theme:
color:
accent: "#7B61FF"
neutralStyle: cool
radius:
base: 16
multiplier: 1
What the operator does
- Keyed by client.
spec.clientIdnames the client, in the organization the resource's namespace configures (spec.organizationwhere several admit it). The server refuses a change to either, so moving a branding to another client is a delete and a new resource. The client does not have to be declared from a manifest, or exist yet: the pages served for thatclient_idwear the document once it does. - Writes against the revision it last wrote. Each write names
status.etag, the server's revision the resource last recorded. A document changed or removed over the API since is not written over: the resource goes toErrorunderDrifted, naming the revision the server holds. Annotate the resourcenauthera.io/restore(any value) to write the declared document back; the operator removes the annotation when it has. The change is noticed on the operator's next write, that is, when the manifest changes. - Reports a refused document with its field. The resource goes to
ErrorunderRefused, and the message names the field. - Removes the document when the resource is deleted, and the client's pages fall back
to the organization's default. There is no
deletionPolicy: a branding decorates one client's pages and costs nothing to recreate. A finalizer (nauthera.io/branding) holds the resource until the server has removed it; until then the resource is in phaseDeletingwith reasonBrandingNotRemoved. The hourly orphan sweep removes a branding this operator wrote whose manifest went without the finalizer running.
Validation
The resource definition types the whole document. theme is the design system's own
theme by its names: kubectl explain branding. lists every axis with
its enums and bounds, and a field the design system does not know, or a value outside
those, is refused when the resource is applied. The token, custom-property and component
maps keep their values as written, bounded only in number. The server then checks each
of those values against the design system's contract and refuses one the sign-in pages
could not render, naming the field.
The server also refuses a document that is not a JSON object or is larger than 128 KiB.
Every fonts entry must be an https stylesheet on fonts.googleapis.com or
fonts.gstatic.com, and at most 8 are allowed: a stylesheet can restyle anything on the
page, the password field included, so the font CDN is the only host trusted with one.
logoUrl, faviconUrl and hero.imageUrl must be an https URL or an inline
data:image/ URI of a type the pages show (PNG, JPEG, GIF, WebP, SVG or ICO) whose bytes
match that type. supportUrl, termsUrl and privacyUrl must be http or https URLs.
The resource definition holds the URL fields, the https scheme of a font and the font
limit to the same rules at apply time. It does not check a font's host or the 128 KiB
total, so a font from another host, or a document over the total, is applied and then
reported as Refused, with the message naming fonts for a font.
Spec
spec.branding
Type: object | Required
The auth UI branding document. Chrome fields drive screen UI;
theme is the design system's theme (color, typography, radius,
motion, tokens, components, …). The operator serializes this
object to JSON and sends it as BrandingSpec.branding_json.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
appName | string | No | — | App name, shown as "Continue to {appName}". |
companyName | string | No | — | Copyright holder shown in the footer. |
faviconUrl | string | No | — | Icon shown in the browser tab and bookmarks: an https URL or an inline data:image/ URI. |
fonts | []string | No | — | Font stylesheet URLs to load, on fonts.googleapis.com or fonts.gstatic.com only: a stylesheet can restyle the password field, so the font CDN is the one host trusted with it. |
hero | object | No | — | Hero pane content for the split layout. |
layout | string | No | — | Page layout — centered (default) or split. One of: centered, split. |
logoUrl | string | No | — | Logo image rendered in the header: an https URL or an inline data:image/ URI. The /ui CSP img-src blocks http images (mixed content). The admin dashboard shows an inline logo of up to 64 KiB, or one on its own origin, and nothing else. The organization's email shows its logo too; Gmail and Outlook show no SVG, so a PNG suits both. |
mode | string | No | — | Default color mode. One of: system, light, dark. |
privacyUrl | string | No | — | |
productName | string | No | — | Tenant/product name shown in the auth header. |
supportUrl | string | No | — | |
termsUrl | string | No | — | |
theme | object | No | — | The design system's theme, as its defineTheme takes it. A field the design system does not know is refused; the values of its token and component maps are stored as written and checked by the server. |
spec.clientId
Type: string | Required
The OAuth2 client_id this branding applies to.
spec.organization
Type: string | Optional
The slug of the organization this branding belongs to. Needed only where more than one organization admits the namespace in its spec.namespaces; otherwise the one that does is taken. The main organization is "default". Immutable.
Status
phase is one of Pending, Ready, Error or Deleting. The Ready condition's
reason is Registered when the server holds the current generation, Drifted or
Refused as above, NamespaceNotCovered when the resource's namespace is not in the
operator's --namespaces, BrandingNotRemoved while a deletion waits for the server, or
one of the connection and namespace reasons every resource shares (NamespaceNotAdmitted,
OrganizationAmbiguous, OrganizationNotReady, OrganizationChanged; see the
OIDCClient reference). A Restored event records a
document written back over a drifted one.
| Field | Type | Description |
|---|---|---|
clientId | string | The resolved client_id the branding is bound to. |
conditions | []object | Standard Kubernetes conditions (Ready, Synced, …). |
etag | string | The server's revision of this resource, returned on every write and read. A controller compares it against what it last wrote to tell its own change from one made elsewhere, and sends it back as the expected revision so a write that would overwrite someone else's is refused instead. |
issuer | string | The issuer the server homed the branding at, after its default was applied: the organization whose sign-in pages wear it. |
lastReconciledTime | string | |
observedGeneration | integer | .metadata.generation last reconciled by the server. |
organization | string | The slug of the organization the server holds the branding in. Once set, a namespace that comes to configure another organization is refused rather than moving the branding. |
phase | string |