BluAuth
Docs
Sign in
User FAQ
  • Reset my password
  • I can't sign in
  • Didn't get reset email
  • Account linking
  • Session expiry
  • Two-factor auth
Admin Guides
Theme Studio
  • Overview
  • Layouts
  • Styling tokens
  • Concept copy
  • Assets & backgrounds
  • Advanced CSS
Admin Shell
  • Users
  • Providers
  • Clients
  • Invitations
Integrations
  • OIDC flow
  • Legacy OAuth flow
  • Provider token brokering
  • Email triggers
  • Webhook events
  • Session contract
Reference
  • API
  • Error codes
  • Event shapes
  • Design tokens
Runbooks
  • Deployment
  • Local operations

OAuth Clients

Every downstream Blu application that authenticates through BluAuth needs an OAuth client record. The client record is what actually ties a tenant together — each client represents one app, binds to one theme, binds to a subset of providers, and defines which redirect URIs BluAuth is willing to hand users back to.

Clients live at /admin/clients. The list shows every client in the tenant; the detail page at /admin/clients/:id is where most configuration happens.

What a client record carries

FieldPurpose
NameHuman-readable label shown in admin UI and invitation emails.
SlugURL-safe identifier — used in URLs like /login?client_slug=bucket-indexer.
Client IDOIDC client_id — issued by BluAuth, cannot be changed.
Client Typepublic (PKCE-only, SPAs / mobile) or confidential (has a secret, server apps).
Client SecretFor confidential clients only. Shown once at rotation; not retrievable.
Redirect URIsExact-match allowlist of URLs BluAuth will redirect to after auth.
Allowed ScopesWhich OIDC scopes the client may request.
Access Token TTLHow long access tokens issued for this client live. Default: 1 hour.
Refresh Token TTLRefresh token lifetime. Default: 30 days.
Bound ThemeThe hosted login experience for users of this client. See theme studio.
Bound ProvidersThe subset of tenant-wide providers exposed on this client's login page.
PKCE RequiredOn for public; configurable on for confidential.

Creating a client

From /admin/clients:

  1. Click New Client.
  2. Enter a Name (e.g. "Bucket Indexer — Production") and a Slug (URL-safe, lowercase, dashes — e.g. bucket-indexer-prod).
  3. Pick a Client Type:
    • Public — Single-page apps, mobile apps. Uses PKCE only (no secret). Recommended for any client that runs in a browser or untrusted environment.
    • Confidential — server-to-server apps, trusted backends. Generates a client secret at creation.
  4. Set at least one Redirect URI (see below).
  5. Save.

After creation, the detail page opens. For confidential clients, the secret is shown once in a banner — copy it immediately. BluAuth does not store the plaintext; if you lose it, you must rotate.

Client naming conventions for discoverability

Use these conventions — they aren't enforced, but they keep the admin UI navigable as your tenant grows:

  • Name format: {App Name} — {Environment} (e.g. "Bucket Indexer — Staging", "Bucket Indexer — Production").
  • Slug format: {app-name}-{env} (e.g. bucket-indexer-staging, bucket-indexer-prod).
  • Environment per client: don't reuse one client across staging and production. Separate clients make redirect URIs cleaner and avoid accidental cross-environment token issuance.
  • One client per app, per environment. Resist the urge to make a "shared" client for multiple apps — theme and provider binding are per-client, so sharing a client means sharing a login experience.

Redirect URIs

BluAuth enforces exact-match on redirect URIs. If the requested redirect_uri doesn't string-match an entry in the allowlist, the authorize call returns invalid_redirect_uri — no substitution, no normalization.

Rules

  • Must use https:// in production.
  • http://localhost:* is allowed — any port, any path. Useful for local development without registering every port.
  • Query strings and fragments are not part of the match — BluAuth ignores them during comparison. The path and trailing slash do matter.
  • Wildcards are not supported. Register each exact URI.

Common matching gotchas

  • https://app.example.com/callback will not match https://app.example.com/callback/ (trailing slash). Register both if both are used.
  • Case sensitivity: host is case-insensitive, path is case-sensitive.
  • Scheme is required — app.example.com/callback is not a valid entry.

Allowlist syntax

Each entry is a single URI. You can register multiple:

https://app.example.com/callback
https://app.example.com/oauth/callback
http://localhost:3000/callback
http://localhost:4200/callback

Add them in the Redirect URIs panel on the client detail page. Changes apply immediately.

Client secrets (confidential clients)

Confidential clients have a client secret that's sent alongside the client ID when exchanging an auth code for tokens. The secret is encrypted at rest with AWS KMS (same pipeline as provider secrets — see the providers guide).

Shown once

At creation and at rotation, the plaintext is shown in a banner at the top of the detail page. Copy it immediately — BluAuth stores only the hashed/encrypted version; the UI cannot show it again.

Rotation

Click Rotate Secret on the detail page. BluAuth:

  1. Generates a new secret.
  2. Encrypts and stores it.
  3. Invalidates the old secret immediately — not after a grace period.
  4. Displays the new plaintext once.

Because rotation is immediate, coordinate with the downstream app's deploy. Recommended flow:

  1. Rotate in BluAuth.
  2. Deploy the new secret to the downstream app's environment.
  3. Verify the app can still mint tokens.

If you need a zero-downtime rotation, register two clients temporarily: the app reads from both, you cut over, then delete the old one.

Compromised secrets

If you suspect a secret is compromised, rotate immediately — don't wait for a deploy window. The minutes of downtime until the downstream app re-deploys with the new secret are vastly preferable to the open attack surface.

PKCE

PKCE is required for all public clients. The code_challenge + code_verifier pair on the authorize / token requests is the only proof of possession a public client has — without PKCE, a browser-based app has no way to defend against an intercepted authorization code.

For confidential clients, PKCE is optional but recommended. Many modern backend apps combine client authentication (the secret) with PKCE; it adds defense in depth at negligible cost. The Require PKCE toggle on the detail page enforces PKCE on confidential clients.

BluAuth supports S256 (SHA-256) and plain code challenge methods. Prefer S256; plain is available only for legacy compatibility and should not be used for new integrations.

Scopes

BluAuth supports the standard OIDC scopes:

ScopeGrants
openidRequired for every request. Issues an id_token.
profilename, preferred_username, picture claims in the id_token and /userinfo.
emailemail, email_verified claims.

The allowedScopes field on the client limits what the client is permitted to request. If the client asks for a scope not on the list, the authorize call returns 400 with invalid_scope. Keep this list minimal — a client that only needs openid email should have exactly that, nothing more.

Access token TTL

Default: 1 hour (3600 seconds). Configurable per client in the Token Lifetimes panel.

  • Minimum: 60 seconds.
  • Maximum: 24 hours.
  • Longer TTLs reduce refresh churn but extend the blast radius of a leaked token.

For high-security tenants, shorten to 15 minutes and rely on silent re-auth through the BluAuth session for longevity. For low-churn internal tools, the default is fine.

Client types: confidential vs public

TraitConfidentialPublic
Has a secretYesNo
PKCEOptional (recommended)Required
Typical deploymentServer apps, trusted backendsSPAs, mobile apps, CLI tools
Client credentials grantSupportedNot supported
Refresh tokensNot issued todayNot issued today

Rule of thumb: if the app runs on a machine you control and users can't see, it's confidential. If it runs on a device the user holds, it's public.

Client credentials grant

Confidential clients can use the client_credentials grant for service-to-service tokens with no user involved. The issued access token carries sub = clientId and all scopes the client is permitted to request. Use for:

  • Machine integrations — webhooks, cron jobs, internal APIs.
  • Non-user traffic — log ingestion, metrics pipelines.

Do not use for user-facing flows — machine tokens cannot represent a user and downstream apps that assume a sub points to a user will break.

Bound theme

Every client has exactly one bound theme. The theme drives the hosted login experience (layout, colors, copy, assets) for users authenticating against this client.

  • Set the binding on the client detail page's Theme section.
  • If no theme is bound, the default service theme (marked isDefault) is used.
  • One theme can be bound to many clients — useful when two apps share branding.

See the theme studio overview for how themes resolve and what they control.

Bound providers

From the client detail page's Providers section, check the boxes for every tenant-wide provider you want to expose on this client's login page. Drag to reorder — the first provider in the list is the first button users see.

At least one sign-in method (a bound provider or the local password field) must be available; otherwise users have no way in.

See the providers guide for how providers are configured tenant-wide before they can be bound.

Integration guide tab

The client detail page includes an Integration Guide tab with a copy-ready OIDC configuration for your app developers:

  • Issuer — https://auth.blutools.io/ (or the tenant subdomain).
  • Authorization endpoint — https://auth.blutools.io/oauth2/authorize.
  • Token endpoint — https://auth.blutools.io/oauth2/token.
  • Userinfo endpoint — https://auth.blutools.io/oauth2/userinfo.
  • JWKS — https://auth.blutools.io/.well-known/jwks.json.
  • Discovery — https://auth.blutools.io/.well-known/openid-configuration.
  • Client ID — this client's ID.
  • Allowed scopes — copy of the list.
  • PKCE requirement — yes/no.

Hand this to your app developer as-is. Their OIDC library will pick up the rest from discovery.

Deletion

Deleting a client removes the client record, invalidates every token issued for it, and removes its theme and provider bindings. Existing user records and session history survive.

Deletion is irreversible. Prefer disabling (an Enabled toggle on the detail page) if you might bring the app back — disabled clients reject all authorize calls with invalid_client but remain in the UI for reference.

Common failure modes

SymptomLikely cause
invalid_redirect_uri at authorizeRedirect URI not on the allowlist — check trailing slashes and case.
invalid_client at token endpointClient secret wrong, rotated, or client disabled.
invalid_scope at authorizeRequested scope not in allowedScopes.
Public client signs in once but silent re-auth failsPKCE verifier/state handling is broken in the client, or the app is not re-entering the authorize flow after access-token expiry.
Login page has no buttonsNo providers bound, no local password allowed — bind at least one.
Wrong theme rendersTheme binding stale or client slug typo in the upstream app's authorize URL.

Next

  • Theme studio overview — design the hosted login for this client.
  • Providers — configure the providers you'll bind here.
  • Invitations — onboard users against this client.

On this page

  • What a client record carries
  • Creating a client
  • Client naming conventions for discoverability
  • Redirect URIs
  • Rules
  • Common matching gotchas
  • Allowlist syntax
  • Client secrets (confidential clients)
  • Shown once
  • Rotation
  • Compromised secrets
  • PKCE
  • Scopes
  • Access token TTL
  • Client types: confidential vs public
  • Client credentials grant
  • Bound theme
  • Bound providers
  • Integration guide tab
  • Deletion
  • Common failure modes
  • Next
DocsPrivacyTerms
© 2026 Blu Digital Group