Applications & clients

An application groups one or more clients inside a realm. A client is the OIDC relying party your software authenticates as — it has a client_id, an authentication method, redirect URIs, grant types, and scopes.

flowchart TD Realm["Realm"] --> App["Application: 'Acme Web'"] App --> C1["Client: SPA<br/><small>auth_method = none (PKCE)</small>"] App --> C2["Client: Backend<br/><small>auth_method = client_secret</small>"] App --> C3["Client: Service worker<br/><small>auth_method = private_key_jwt</small>"]

Create an application

From a realm, open Apps and use Create Application. Fields:

Name (required)Display name, e.g. Acme Web.
DescriptionOptional free text.
Terms & Conditions URLOptional link.
Privacy Policy URLOptional link.

After creation you land on the application's clients page, where you add clients.

Create a client

Use the Add Client form. Fields and their exact behavior:

FieldNotes
Label (required)Human display name (e.g. Backend, SPA, Mobile). The client_id itself is generated by SemAuth — you don't choose it.
Auth MethodOne of client_secret (default), none (public), or private_key_jwt. This is fixed after creation — to change it, create a new client.
Redirect URI (required)Where SemAuth sends the user back after login. One URI here on creation; add more on the edit page.
JWKS URIOnly for private_key_jwt. The HTTPS URL where you publish your public keys.
Grant typesSpace- or comma-separated. Defaults to authorization_code. Valid values: authorization_code, refresh_token, client_credentials.
ScopesSpace- or comma-separated. Defaults to openid. e.g. openid email profile offline_access.
Allowed API audiencesSpace- or comma-separated. The API audiences this client may request (see the APIs catalog). Every value must already exist in your org's visible APIs.

Watch the separators. Grant types, scopes, and audiences are split on spaces and commas. Make sure each value is its own token — a stray formatting mistake can register one combined scope that then fails at /authorize.

Public vs confidential — derived from the auth method

There is no separate "public/confidential" switch. It follows the auth method:

The one-shot creation screen

Right after creating a client you see its Client ID (with a copy button) and, for a client_secret client, the Client Secret labeled "save this, shown only once". This page is served with no-cache headers and the secret can never be retrieved again — copy it now. For none and private_key_jwt clients there is no secret to show.

Editing a client

Open a client's Edit page. You can change:

The auth method is immutable (changing it would invalidate the secret) and there is no secret-rotation control on this page — create a new client if you need a different method or a fresh secret.

Testing a client

The Test link next to each client launches the real authorization-code + PKCE flow against the client's first registered redirect URI (with prompt=login to force a fresh login). It's the quickest way to confirm a client is wired up correctly end-to-end before integrating your code.

Client authentication methods at /token

How each method proves the client's identity when exchanging a code or requesting tokens:

none — public client + PKCE

No client credential. The authorization code is bound to a PKCE challenge at /authorize; at /token the client must present the matching code_verifier. Only S256 is accepted. Use this for SPAs, mobile, and CLIs.

client_secret

The client presents its secret, verified against the stored Argon2 hash. Two transports are accepted (both map to the same method — pick whichever your library uses):

private_key_jwt

The client signs a short-lived JWT assertion with its private key and sends it as client_assertion (with client_assertion_type = urn:ietf:params:oauth:client-assertion-type:jwt-bearer). SemAuth fetches your public keys from the registered JWKS URI and verifies the signature. The assertion must be RS256 or ES256; its aud must be the token endpoint URL; iss and sub must both equal the client_id; and each jti is single-use (replays are rejected). Your private key never leaves your infrastructure.

Redirect URI validation

At /authorize the requested redirect_uri must exactly match a registered URI — this is the normal case for all web apps. There is one relaxation, for native/CLI clients (RFC 8252 §7.3): if you register the bare loopback host http://localhost or http://127.0.0.1, then any port and path on that loopback host is accepted at runtime (http://localhost:54321/cb). This is what lets a desktop CLI pick a free port on the fly. Anything that isn't loopback http must match exactly.

Choosing the right client for your app

Your appAuth methodGrant types
Server-side web appclient_secret or private_key_jwtauthorization_code refresh_token
Single-page app (SPA)noneauthorization_code refresh_token
Mobile / desktop / CLInone (PKCE + loopback)authorization_code refresh_token
Machine-to-machine (no user)client_secret or private_key_jwtclient_credentials

Note: offline_access in the requested scopes is what makes the authorization-code flow return a refresh token. See the OIDC reference for token details.