---
title: "OIDC applications"
description: "Sign people in to your own web, single-page and native apps with OpenID Connect, and configure their clients, tokens and claims."
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.ezghcloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# OIDC applications

An `oidc` application is an OpenID Connect client: your own app, or a SaaS app that supports
OIDC, signing your directory's people in. Each directory is its own OpenID Provider, with the
issuer `https://<alias>.sso.ezgh.app` ([endpoints](/sso/oidc-endpoints)). Only users you
[assign](/sso/assignments) to the application, directly or through a group, can sign in to it.

A directory can have up to 200 applications, of every kind. You need `sso.applications.create`.
For SAML 2.0 service providers, see [SAML applications](/sso/saml-applications); to push an
application's assigned users to it over SCIM, see [Outbound provisioning](/sso/provisioning).

## Application types

| `applicationType` | For | Client authentication (`tokenEndpointAuthMethod`) |
| --- | --- | --- |
| `web` | Apps with a server that keeps a secret | `client_secret_basic` (default), `client_secret_post` or `private_key_jwt` |
| `spa` | Single-page apps in the browser | `none`; PKCE (`S256`) required |
| `native` | Desktop, mobile and command-line apps | `none`; PKCE (`S256`) required |

The type can't change after creation. Every application uses the authorization code flow;
`web` applications may use PKCE too, and it's checked whenever it's sent.

## Create an application

In the console, open **EZGH SSO** > **Applications**, select **Create application**, and choose
**Web application**, **Single-page application** or **Native application**.

```sh
ezgh sso applications create --name "Wiki" --type web --redirect-uri https://wiki.acme.com/callback
```

Or call [CreateApplication](/sso-api/CreateApplication/):

```json
{
  "name": "Wiki",
  "kind": "oidc",
  "oidc": {
    "applicationType": "web",
    "redirectUris": ["https://wiki.acme.com/callback"]
  }
}
```

The answer is `{ "application", "clientSecret" }`. The client ID is `application.oidc.clientId`,
the application's ID. For `client_secret_basic` and `client_secret_post`, `clientSecret` is the
first client secret, `ezgh_scs_` and 43 letters and digits. **It's shown only in this answer**
(and replays of it with the same `Idempotency-Key`); store it now.

Then [assign](/sso/assignments) users or groups, and configure your app with the issuer, the
client ID and the secret. Most OIDC libraries need only the issuer: they read the rest from
discovery.

## Settings

`oidc` holds the client's settings. On update, only the fields you send change, and `null`
clears an optional one.

| Field | Description | Default |
| --- | --- | --- |
| `redirectUris` | 1 to 20 exact redirect URIs. See [Redirect URIs](#redirect-uris). | Required |
| `postLogoutRedirectUris` | Up to 20 exact URIs that [logout](/sso/oidc-endpoints#logout) may return to. | None |
| `initiateLoginUri` | An https URL the portal tile opens. See [Portal tiles](#portal-tiles). | `null` |
| `backchannelLogoutUri` | A public https URL that receives back-channel logout tokens. See [Logout](#logout). | `null` |
| `jwks`, `jwksUri` | The client's public keys, for `private_key_jwt` only (exactly one of them). See [private_key_jwt](#private_key_jwt). | `null` |
| `idTokenSignedResponseAlg` | `RS256` or `ES256`. Signs the ID token and the access token. | `RS256` |
| `accessTokenLifetimeSeconds` | The access token's lifetime: 300 to 3,600. | 900 |
| `accessTokenAudience` | The access token's `aud`: 1 to 256 characters without spaces. `null` is the client ID. | `null` |
| `groupsClaim` | Which groups the `groups` claim lists: `assigned` (the user's groups that are assigned to this application) or `all` (all their groups). | `assigned` |

The answer also shows `issuer`, `clientId`, `clientSecrets` (metadata only), and for `jwksUri`
when the keys were last fetched (`jwksFetchedAt`) and the last fetch's error (`jwksFetchError`).

### Redirect URIs

Redirect URIs match exactly: no wildcards and no fragments.

- `https` URLs.
- `http` only on a loopback address (`127.0.0.1`, `[::1]` or `localhost`).
- `native` applications may also use a private-use scheme in reverse-domain form
  (`com.acme.app:/callback`), and a loopback IP redirect URI matches any port, as RFC 8252 says.

## Client secrets

`web` applications with `client_secret_basic` or `client_secret_post` authenticate with a client
secret. An application has at most two, so you can rotate without downtime:

1. [CreateApplicationSecret](/sso-api/CreateApplicationSecret/) creates a second secret,
   shown once (`409 too_many_secrets` when there are already two).
2. Deploy it to your app.
3. [DeleteApplicationSecret](/sso-api/DeleteApplicationSecret/) deletes the old one, which
   stops working at once.

```sh
ezgh sso applications secrets create Wiki
ezgh sso applications secrets delete Wiki <secret ID or prefix>
```

`clientSecrets` lists each secret's ID, `prefix` (its first 13 characters), creator, creation
time and when it was last used. Secrets are stored only as hashes: a lost secret can't be shown
again.

### private_key_jwt

A `web` application can instead sign a JWT with its own private key (RFC 7523) and send it as
`client_assertion`, with `client_assertion_type`
`urn:ietf:params:oauth:client-assertion-type:jwt-bearer`. Give its public keys as one of:

- `jwks`: a JWK set by value, `{ "keys": [...] }`, of 1 to 10 RSA (2048 bits or more) or EC
  (P-256, P-384, P-521) signing keys, each with a `kid` when there are several;
- `jwksUri`: a public https URL of the JWK set. It's fetched hourly, and again (at most once a
  minute) when an assertion names a `kid` it doesn't have.

The assertion's `iss` and `sub` are the client ID, its `aud` the issuer or the token endpoint;
it lives at most 10 minutes and can be used once. Algorithms: RS256–512, PS256–512, ES256–512.

## Tokens

| Token | Format | Lifetime |
| --- | --- | --- |
| Authorization code | Opaque | 60 seconds, single use. A second use revokes the grant. |
| ID token | JWT signed with `idTokenSignedResponseAlg` | 1 hour |
| Access token | JWT, RFC 9068 (`typ: at+jwt`) | `accessTokenLifetimeSeconds`, 15 minutes by default |
| Refresh token | Opaque, `ezgh_srt_…`; only with the `offline_access` scope | Rotated on every use. 30 days without use, and 90 days at most from sign-in. |

- A refresh token can be used once: its answer carries a new one. Presenting a used refresh
  token again revokes the grant, and every token in it.
- A refresh may ask for fewer scopes, never more.
- Every code exchange and refresh checks that the user is still active and still assigned; if
  not, the grant is revoked.
- Your APIs can validate access tokens with the directory's [JWKS](/sso/oidc-endpoints#jwks), or
  call [introspection](/sso/oidc-endpoints#introspection) to see revocation at once.
- Signing keys rotate every 90 days. The next key is published in the JWKS 7 days before it's
  used, and the old one stays there 7 days after. Read keys from the JWKS by `kid`; don't pin them.

### Scopes and claims

| Scope | Claims (ID token and userinfo) |
| --- | --- |
| `openid` | Required for an ID token: `sub` (the user's ID) |
| `profile` | `name`, `preferred_username` (the email address), `given_name`, `family_name` (when set), `updated_at` |
| `email` | `email`, `email_verified` (`true` once the user has activated) |
| `groups` | `groups`: group names, filtered by `groupsClaim` |
| `offline_access` | A refresh token |

The **ID token** also has `iss`, `aud` (the client ID), `azp`, `exp`, `iat`, `auth_time`,
`nonce` (when sent), `acr`, `amr`, `sid` (the portal session's ID) and `at_hash`. A refreshed ID
token keeps `auth_time` and has no `nonce`.

The **access token** has `iss`, `sub`, `aud` (`accessTokenAudience`, or the client ID),
`client_id`, `jti`, `iat`, `exp`, `auth_time`, `acr`, `amr`, `scope`, `sid`, and `groups` with
the `groups` scope.

| Claim | Values |
| --- | --- |
| `amr` | `pwd` (password), `otp` (TOTP), `hwk` (passkey), `fed` (identity provider), `mfa` (identity provider MFA you trust) |
| `acr` | `urn:ezgh:sso:acr:mfa` for a passkey, two factors, or trusted upstream MFA; otherwise `urn:ezgh:sso:acr:sfa` |

## Logout

- **RP-initiated logout:** send the person to the [logout endpoint](/sso/oidc-endpoints#logout)
  with `id_token_hint`, and optionally a `post_logout_redirect_uri` registered in
  `postLogoutRedirectUris` and `state`.
- **Back-channel logout:** with `backchannelLogoutUri`, EZGH SSO POSTs an OpenID Connect
  back-channel logout token (`typ: logout+jwt`, with `sid` and `events`) whenever a session the
  application has a grant in ends: logout, sign-out on the account page, signing in again in the
  same browser, an administrator revoking sessions, or the user being suspended, deleted or
  having their factors reset. Answer `200` or `204`. Other answers (except `400`) are retried for
  up to 20 attempts.

Ending a session also revokes the application grants made in it, except those with
`offline_access`. Offboarding a user revokes all of them.

## Portal tiles

With `initiateLoginUri`, assigned users see a tile for the application in the portal. The tile
opens `initiateLoginUri?iss=<issuer>` (OpenID Connect third-party-initiated login), and your app
starts a normal sign-in. Without it, there's no tile.

### Tile logo

Every application, of any kind, can have its own logo on its portal tile. Without one, the tile
shows the application's initials, or, for an EZGH product, the product's own mark. An uploaded
logo replaces either.

Upload it in the console, on the **Logo** card of the application's **Settings** tab, with the CLI, or with
[UpdateApplicationLogo](/sso-api/UpdateApplicationLogo/): `{ "data": "<base64>" }`, a PNG
or JPEG of at most 512 KiB and 1024 × 1024 pixels. It's converted to a PNG. SVG files and links to
images elsewhere are refused. [DeleteApplicationLogo](/sso-api/DeleteApplicationLogo/)
removes it. Both answer the application, and need `sso.applications.update`.

```sh
ezgh sso applications update wiki --logo wiki.png
ezgh sso applications update wiki --remove-logo
```

The application's `logoUrl` is where the logo is served, on your directory's own host:
`https://<alias>.sso.ezgh.app/branding/applications/<id>/logo?v=…`, or `null`. It's read-only, and
a new logo gets a new URL. An application MCP Central manages can't be given a logo
(`409 managed_by_product`).

## Delete an application

[DeleteApplication](/sso-api/DeleteApplication/) revokes every grant and token it holds,
and deletes its secrets, assignments and provisioning settings.

Source: https://docs.ezghcloud.com/sso/applications/index.mdx
