AUTH4D-139 application logo

Each public application (browser or native) can have one logo. The hosted login, guest upgrade and device approval pages show it next to the application name, with the application name as its alt text. Verification emails are unchanged. Logos are stored in application_logos (migration 0014) and are deleted with their application’s tenant.

Rules

  • PNG or WebP only. The type is decided by the file’s magic bytes, which must match the request’s Content-Type. SVG and every other format is rejected.
  • At most 65536 bytes (64 KB) and at most 512 by 512 pixels, read from the PNG IHDR chunk or the WebP VP8X, VP8L or VP8 header. A PNG must end with its IEND chunk and a WebP’s RIFF length must match the body.

Upload

PUT /management/tenants/{tenantId}/clients/{clientId}/logo (scope client:write, project role owner or admin; a viewer gets 403 insufficient_role)

The body is the raw image, sent with Content-Type: image/png or image/webp. These are the only non-JSON management bodies the API boundary accepts. The response is sent with no-store:

{
  "logo": {
    "content_type": "image/png",
    "byte_length": 2048,
    "width": 256,
    "height": 256,
    "version": "<base64url SHA-256 of the image, 43 characters>",
    "updated_at": "2026-10-06T12:00:00.000Z"
  }
}

The upload replaces any earlier logo. Uploading the stored image again changes nothing and writes no second audit event. Failures write nothing:

Status Code Cause
415 unsupported_media_type The Content-Type is not image/png or image/webp (for example SVG)
413 request_too_large The body is larger than 65536 bytes
415 logo_unsupported_type The bytes are not a PNG or WebP file
400 logo_type_mismatch The bytes are PNG but the Content-Type says WebP, or the reverse
400 logo_malformed The image header or trailer cannot be read
400 logo_dimensions The image is wider or taller than 512 pixels
400 logo_empty The body is empty

Management errors use the AUTH4D-18 error shape, with details[].field set to logo.

Preview and removal

  • GET /management/tenants/{tenantId}/clients/{clientId}/logo (scope client:read, any project role) returns the stored image with its Content-Type, X-Content-Type-Options: nosniff and no-store, or 404 not_found when the application has no logo. The console shows it through the BFF.
  • DELETE /management/tenants/{tenantId}/clients/{clientId}/logo (scope client:write, owner or admin) removes the logo and returns {"logo": null}. Removing an absent logo also returns that.
  • The application document (GET or PATCH .../clients/{clientId}, see AUTH4D-56 (repository file docs/api-spec/auth4d-56.md)) gains "logo": the metadata above, or null.

Through the console BFF these routes are /api/management/... and, like every BFF write, upload and removal require the console Origin (CSRF) and the session cookie.

Audit

Upload and removal each write one management.client.presentation.updated event for the client, with changed_fields: "branding.logo" and logo: "uploaded" (plus content_type) or logo: "removed". The image, its digest and its size are never written to the audit log.

GET /t/{tenantId}/clients/{clientId}/logo?v={version} on the authentication origin serves the logo of an enabled browser or native application of an active customer tenant. Anything else, a v that is not the current version, or an unknown query parameter returns 404 with no-store.

Header Value
Content-Type image/png or image/webp, as stored
X-Content-Type-Options nosniff
Cache-Control public, max-age=31536000, immutable with the current v; public, max-age=0, must-revalidate without v
ETag "{version}"; a matching If-None-Match returns 304
Content-Security-Policy default-src 'none'; sandbox
Cross-Origin-Resource-Policy same-origin

A new upload changes the version, so a replaced logo is never served from an old URL.

Hosted pages

GET /t/{tenantId}/login/context and GET /device/verify add branding.logo_url when the application has a logo. It is the same-origin path above, for example /t/ten_…/clients/cli_…/logo?v=… (ApplicationLogoPathSchema in packages/contracts). The field is absent otherwise, so earlier responses are unchanged. The hosted login only uses a path whose tenant and client match the page’s own. The hosted-login Content Security Policy is unchanged (img-src 'self').

Source: docs/api-spec/auth4d-139.md