Skip to content

Clients

A client represents an application that requests access to protected resources.

This access can either be on behalf of a user (using the authorization code flow with PKCE) or for the client itself (using the client credentials flow).

Clients can be created through the admin console or dynamically via the dynamic client registration endpoint (RFC 7591).

Clients can be either public or confidential, depending on whether they can securely store credentials.

Public clients are for applications that cannot keep secrets confidential:

  • Single-page applications (SPAs) - JavaScript code runs in the browser where all secrets are visible to users
  • Mobile apps - APK/IPA files can be decompiled, exposing any embedded secrets
  • Desktop applications - Binaries can be reverse-engineered to extract credentials

Public clients must use the authorization code flow with PKCE and cannot use client credentials flow.

Confidential clients are for applications that can securely protect credentials:

  • Server-side web applications - Backend code runs on servers not accessible to end-users
  • Backend services - APIs and microservices with secure credential storage
  • Server-to-server integrations - Services running in controlled environments

Confidential clients can safely store client secrets and use both authorization code flow and client credentials flow.

Which should you choose? If your application code runs entirely in the browser or on user devices, you must use a public client. If you have a backend server that handles OAuth flows, use a confidential client for better security.

In OAuth2, the consent process ensures that users explicitly authorize third-party applications to access their resources.

When the client is affiliated with the same organization as the authorization server and a high level of trust exists, explicit consent is not usually required.

However, for clients from third-party organizations, configure the client to request user consent. This ensures that users are aware of who is accessing their tokens.

The default differs by how the client was created. A client an administrator creates, through the admin console or the admin API, starts with consent off: an administrator vouched for it. A client that registered itself through dynamic client registration starts with consent on, because nobody has reviewed it. You can untick it on that client’s settings page once you have reviewed it.

ACR stands for “Authentication Context Class Reference.” It specifies the level of authentication assurance or the strength of the authentication method used to authenticate the end-user.

Goiabada has 3 levels:

ACR levelDescriptionWhen to use
urn:goiabada:level1Level 1 authentication only (password)Low-security resources, read-only access
urn:goiabada:level2_optionalLevel 1 with optional 2FA (if enabled by user)Balanced security, respects user preferences
urn:goiabada:level2_mandatoryLevel 1 with mandatory 2FAHigh-security resources, admin operations, financial transactions

By default, a client comes configured with urn:goiabada:level2_optional, which provides a good balance between security and user experience.

You can override the client’s default ACR level on a per-authorization basis. For example, if your client has the default urn:goiabada:level2_optional but you have a specific resource that requires 2FA, you can specify urn:goiabada:level2_mandatory in the acr_values parameter of the authorization request.

Each client can be configured with display metadata that is shown to users during authentication and consent screens:

  • Display name - A human-friendly name shown instead of the client identifier (e.g., “My Application” instead of my-app). If the display name is empty, the client identifier is shown as a fallback.
  • Description - A short description of the client, shown on consent and auth screens when enabled.
  • Website URL - A link to the client’s website, shown on the consent screen when enabled. Must use http or https scheme.

Each of these has a corresponding visibility toggle (showDisplayName, showDescription, showWebsiteURL) that controls whether it appears on auth/consent screens. There is also a showLogo toggle for controlling logo visibility (see below).

Self-registered clients are named differently on the consent screen. A client created through dynamic client registration supplies its own name at registration, and the consent screen shows that name with a note saying it has not been verified. Give the client a display name and turn on showDisplayName to replace it, with no note. The password and OTP screens are not affected and keep showing whatever the display settings above select.

These settings can be managed through the admin console under the client’s Settings tab, or via the REST API.

Each client can have a logo image that is displayed on authentication screens (password, OTP, consent).

Logos can be uploaded and managed through:

  • The Logo tab in the admin console client settings
  • The REST API endpoints

Client logos are publicly accessible at:

GET /client/logo/{clientIdentifier}

This endpoint requires no authentication and returns the image directly with appropriate content type headers. It returns 404 if the client or logo does not exist. The response includes ETag and Cache-Control headers for efficient caching.

Constraint Value
Formats JPEG, PNG, GIF, WebP
Dimensions 10x10 to 512x512 pixels
Max size 3MB

The admin-console-client is a system-level client used internally by the admin console. It has special protections:

  • Its client identifier cannot be changed (renaming is blocked)
  • It cannot be deleted
  • All other settings (description, display name, redirect URIs, etc.) can be modified normally

The API response for this client includes "isSystemLevelClient": true to indicate its protected status.

In the Authorization code flow with PKCE, the client application specifies a redirect URI in its authorization request.

After the user grants or denies permission, the authorization server redirects the user back to this specified URI.

It’s necessary to pre-configure this URI in the client, and only exact matches are accepted (no wildcards). The single exception is the port of an http loopback address, described below.

A native or desktop app receives its callback on a temporary HTTP server bound to a port the operating system assigns at startup. The app cannot know that port when the client is registered, so RFC 8252 requires the authorization server to accept any port on a loopback redirect URI.

Register the URI without a port:

http://127.0.0.1/callback

Any port is then accepted at authorization time, so http://127.0.0.1:54321/callback matches. The loopback hosts are 127.0.0.1, ::1 (written as http://[::1]/callback) and localhost.

The port is the only component that may differ. Scheme, host, path, query and fragment are still compared byte for byte, including character case and percent-encoding. The code is delivered to the port the client actually requested, and that same URI must be presented at the token endpoint.

Three limits apply:

  • http only. An https loopback URI still requires an exact match, since RFC 8252 defines loopback redirects as using http.
  • response_type=code only. The implicit flow gets no port flexibility, because it returns tokens directly in the redirect and interception cannot be mitigated.
  • PKCE is required for public native clients. Port flexibility widens the set of local ports that may receive an authorization code, and PKCE is what makes an intercepted code useless. Enable it globally under SettingsGeneralPKCE required for authorization code flow, or per client under the client’s OAuth2 flows page. See PKCE.

If the client registers itself through dynamic client registration rather than being created in the admin console, additional restrictions apply to its redirect URIs. See Redirect URI rules.

If your client application plans to make calls to the /token, /logout or /userinfo endpoints from JavaScript, you must register the URL (origin) of the web application to enable Cross-Origin Resource Sharing (CORS) access. Failure to do so will result in CORS blocking the HTTP requests.

Client permissions are used in server-to-server checks, specifically within the client credentials flow. This is about the permissions granted to the client itself, allowing it to access other resources.

Each client can override global token settings to meet specific application requirements. In the client’s Tokens settings, you can customize:

  • Token expiration - Override the global access/ID token lifetime
  • Refresh token timeouts - Configure offline refresh token idle timeout and max lifetime
  • OIDC claims in tokens - Control whether OpenID Connect scope claims (profile, email, phone, address) are included in access tokens and ID tokens

For example, you might:

  • Require shorter token lifetimes for high-security applications
  • Disable OIDC claims in ID tokens for strict OIDC conformance
  • Enable longer refresh token lifetimes for background services

See Tokens for detailed information about token configuration options.

Goiabada supports dynamic client registration according to RFC 7591, allowing applications to self-register as OAuth clients without manual administrator intervention.

This feature is particularly useful for:

  • MCP (Model Context Protocol) servers - AI tools and IDE extensions that need OAuth credentials
  • Native desktop applications - Apps that need unique client credentials per installation
  • Development tools - CLI tools, testing frameworks, and developer utilities
  • Automated deployments - CI/CD pipelines that provision OAuth clients automatically

By default, this feature is disabled for security reasons. To enable it:

  1. Navigate to Settings → Dynamic Client Registration in the admin console
  2. Enable the feature
  3. The registration endpoint becomes available at /connect/register

When enabled, any application can register as a client. Consider these security measures:

  • Limit to trusted networks - Use firewall rules or reverse proxy to restrict access
  • Monitor registrations - Regularly audit newly registered clients in the admin console. The client list marks each one with a Self-registered badge, so you can tell at a glance which clients arrived through this endpoint and which an administrator created
  • Review client metadata - Check redirect URIs and other metadata of self-registered clients
  • Disable untrusted clients - Clients can be disabled through the admin console if needed

A self-registered client’s identifier cannot be changed, in the same way the system-level client’s cannot: it is generated rather than chosen, so the name in your client list is always the name the client authenticates under. Every other setting can be changed, including Consent required.

Self-registered clients require consent by default, and the consent screen shows their self-asserted name with a note saying it is unverified. Clients an administrator creates are unaffected and keep their existing default of consent off.

A refused authorization is not redirected back to a self-registered client. Anyone can register a client and point it at any address, so an error response is an instruction to send someone’s browser to a site nobody here has checked. When Goiabada refuses an authorization for a self-registered client, whether the user declined it or the request was invalid, it shows the user a page naming the application and the address it asked to send them to, and the request stops there. Clients an administrator created keep the ordinary redirect.

Silent authentication with prompt=none is covered rather than exempt: it is the version of the same attack that needs no session and nothing from the user. Inside the hidden iframe silent renewal normally runs in, nothing is displayed to anyone.

Dynamic registration validates redirect URIs more strictly than the admin console does, because the caller is anonymous. What is accepted depends on whether the client is public, which is determined by token_endpoint_auth_method:

Client type Allowed Not allowed
Public (token_endpoint_auth_method: none) http on 127.0.0.1, ::1 or localhost, with any port; custom schemes such as myapp://callback https; http on any other host
Confidential (any other auth method) https on any host; http on the three loopback hosts custom schemes; http on any other host

Four rules apply to both client types:

  • The host is matched exactly, after case folding. A host that merely starts with a loopback name, such as localhost.attacker.com, is not a loopback host and is rejected.
  • The URI must be absolute, with a scheme, per RFC 6749 section 3.1.2. Values like //example.com/callback and /callback are rejected.
  • A fragment is not permitted, per the same section. http://127.0.0.1/callback#done is rejected. A percent-encoded %23 is not a fragment and is fine.
  • Characters that cannot appear in a URI are rejected, namely <, >, ", {, }, |, \, ^, backtick and space. Percent-encode them if you genuinely need them.

Some schemes are refused outright, either because a browser cannot deliver an authorization response to them or because they can execute script: javascript, data, vbscript, file, blob, about, chrome, chrome-extension, moz-extension, view-source, filesystem, resource, ftp, ftps, ws, wss, gopher and telnet.

For loopback redirect URIs, the port flexibility described under Loopback redirect URIs for native apps applies here too: register http://127.0.0.1/callback without a port and any port is accepted at authorization time.

POST /connect/register

The endpoint accepts client metadata as defined in RFC 7591 and returns client credentials.

For detailed API documentation and examples, see the RFC 7591 specification.