Skip to main content

Identity Provider - Client Registration and Administration

This document is the source of truth for registering and administering OAuth 2.0 and OpenID Connect clients in AVA Identity Provider (IDP).

Use the direct client API when the Product catalogue self-service flow cannot create the required client. The most common example is an interactive application that uses the authorization code flow and therefore needs explicit redirect URIs.

Choose the registration path

RequirementRegistration path
Backend service with ClientCredentials and a message-broker accountProduct catalogue OAuth tab or POST /api/v1/Process/ResetSecret/client/{clientId}
Interactive application with AuthorizationCodeDirect Clients API described in this document
Application release that creates IDM rights, navigation, and an optional IDP client togetherIDM POST /api/v1/Process/ApplicationRelease

The Product catalogue OAuth flow creates or resets a confidential service client. It does not send redirect or post-logout URIs and therefore does not create an interactive sign-in client.

Information required from the requester

Do not register a client from only an application URL. Collect and approve the following information first.

InformationRequired decision
ApplicationHuman-readable application name and owning team
Jira issueWork item that authorizes the registration and records rollout evidence
clientIdUnique, stable technical identifier; use the same identifier on every stage
Owner tenantTenant that owns and administers the client
StagesEnvironments where the client must exist
GrantsInteractive, service-to-service, or another explicitly justified flow
Redirect URIsExact absolute callback URIs for every deployment shape on the selected stage
Post-logout URIsExact destinations used after IDP end-session; leave empty when the client does not use provider logout
Requested scopesScopes sent by the application in authorization or token requests
Tenant accessOwner tenant only, explicitly granted tenants, or justified space-wide access
Identity management level1 by default; use 2 only when IDM must not add TenantOwner to a service account
Secret consumerFor confidential clients, the system and owner that will store the one-time secret

Redirect and post-logout URIs are stage-specific. Do not register localhost or non-production hosts in production unless there is an explicit approved requirement.

Concepts and current behavior

Owner tenant and tenant access

A client is created in the current tenant context. For a normal client, IDP creates an ApplicationTenant relation for that tenant with IsApplicationOwner = true. The owner tenant is therefore selected by the access token used for the create request, not by a field in the JSON body.

The owner tenant controls administration of the client. Reading, updating, deleting, and resetting a client additionally depend on the caller's IDP/IDM authorization and on the ownership checks performed by IDP. A SpaceOwner can administer clients across tenant boundaries.

Client access to a tenant is separate from client ownership. A normal client initially has access only to its owner tenant. Additional tenant access is granted in the target tenant context through the tenant-access endpoint described below.

Grants and client classification

grants is a flags enum. The supported values are:

GrantValuePurpose
Password1User name and password flow retained for compatibility
ClientCredentials2Non-interactive service-to-service authentication
AuthorizationCode4Interactive sign-in with an authorization callback
LoginBehalfOf8Advanced user-context flow; it is not sufficient as the only grant

A client with ClientCredentials is confidential and requires a secret for that flow. A client without ClientCredentials, such as an AuthorizationCode-only client, is public.

An authorization-code client should use PKCE with the S256 challenge method. IDP advertises PKCE but does not currently enforce it for every authorization-code client. The client must therefore request it explicitly.

URI fields

  • redirectUris contains exact OAuth/OIDC callback addresses. IDP requires absolute, well-formed values and rejects duplicates.
  • postLogoutUris contains exact destinations accepted after an IDP end-session request. An OAuth callback URI is not automatically a valid post-logout URI.
  • domains is derived from redirectUris in API responses. Although domain and domains exist in the request contract and are validated, the current create and update handlers do not persist them as allowed CORS origins. Register explicit redirect and post-logout URIs instead.

Front-channel logout callback

frontChannelLogoutUri registers one callback for OpenID Connect Front-Channel Logout through the existing create, update and client-detail endpoints. Unlike postLogoutUris (destinations for the user's browser after logout), this callback is intended for an iframe notification to the application. See the field description in Swagger for validation rules and create/update semantics. Registration alone does not enable logout delivery.

Scopes and token lifetime fields

The current grant setup adds the platform's standard identity and API scope permissions. The current create and update handlers validate duplicate allowedScopes values but do not persist the requested list. Treat allowedScopes as registration intent, not as an effective security allowlist, and verify the scopes requested by the application during integration testing.

The request contract exposes access-token and refresh-token lifetime values. The current create and update handlers do not persist per-client custom lifetime values. Do not depend on those fields without a separately verified implementation change.

Client secrets

IDP generates and stores a hashed secret for every new client, including public clients. The clear-text value is returned only by the create or reset response.

  • A confidential client must capture the value immediately and store it in the application's secret store.
  • A public authorization-code client that uses PKCE does not send a client secret. Ignore and securely discard the generated value; do not add it to frontend or public-client configuration.
  • Losing a confidential client's secret requires a reset, which immediately invalidates the previous value.

Direct API endpoints

All endpoints use this base URL:

https://<stage-host>/api/asol/idp
OperationMethod and path
Read one clientGET /api/v1/Clients/{clientId}
List visible clientsGET /api/v1/Clients
Create clientPOST /api/v1/Clients
Update clientPUT /api/v1/Clients/{clientId}
Delete clientDELETE /api/v1/Clients/{clientId}
Reset secretPOST /api/v1/Clients/{clientId}/ResetSecret
Grant access to the current tenantPOST /api/v1/Clients/{clientId}/GrantTenantAccess
Remove access from the current tenantDELETE /api/v1/Clients/{clientId}/RemoveTenantAccess

The Clients API is protected by IDM authorization. Creation and administration normally require the applicable TenantAdmin/TenantOwner permissions or SpaceOwner access.

Register a client through the direct API

1. Select the owner tenant and stage

Authenticate against the target stage in the tenant that must own the client. Verify the tenant identifier carried by the token before sending a write request.

2. Check that the identifier is available

Call:

GET /api/asol/idp/api/v1/Clients/{clientId}
  • 404 Not Found means no client is visible to the caller. It does not prove global availability when another tenant may own the identifier. Ask a SpaceOwner to verify uncertain identifiers; the create request will still reject a duplicate client id.
  • 200 OK means the client already exists. Do not send a create request. Review the returned configuration and use the safe update procedure if a change is authorized.
  • 403 Forbidden means the caller does not have the required authorization or ownership. Do not treat it as proof that the client does not exist.

3. Create the client

Interactive authorization-code client

The following Alpha example was used to register the Marketplace client. Other stages use the same clientId and stage-specific hosts.

{
"clientId": "marketplace",
"clientName": "Marketplace",
"description": "Interactive OIDC client for AVA Marketplace.",
"grants": "AuthorizationCode",
"redirectUris": [
"http://localhost:3000/api/auth/callback/asol",
"https://alpha.avaplace.com/api/auth/callback/asol",
"https://alpha.avaplace.com/apps/mp/api/auth/callback/asol"
],
"postLogoutUris": [],
"allowedScopes": [],
"identityManagementLevel": 1
}

Do not copy the Marketplace URIs into another application. Derive exact callbacks from that application's deployed authentication configuration.

Confidential service client

{
"clientId": "order-integration",
"clientName": "Order integration",
"description": "Service-to-service client for the order integration.",
"grants": "ClientCredentials",
"redirectUris": [],
"postLogoutUris": [],
"allowedScopes": [
"apigw"
],
"identityManagementLevel": 1
}

Prefer the Product catalogue OAuth flow when the service also needs the standard message-broker account. Use direct creation when the self-service result does not meet the integration requirements.

Send the approved body to:

POST /api/asol/idp/api/v1/Clients
Content-Type: application/json

Expect 201 Created. Send the request once. If the response is lost, use GET to determine whether the client was created instead of retrying blindly.

4. Handle the create response

The response contains the created clientId and a one-time clientSecret. Store the secret only when the client uses a confidential flow. Never copy it into Jira, source control, chat, screenshots, or documentation.

5. Verify the stored configuration

Call GET for the new clientId and compare:

  • clientId and clientName,
  • grants,
  • redirect and post-logout URI lists,
  • identity management level,
  • expected default token lifetime values.

The GET response always redacts the stored secret.

6. Run integration tests

For an interactive client, verify at least:

  • a fresh sign-in without an existing IDP session,
  • SSO after sign-in through another AVA application,
  • the exact callback selected for the deployed path,
  • access-token use by the application,
  • a user from every required tenant-access scenario,
  • logout when the application uses IDP end-session.

Complete one stage before repeating the registration on the next stage.

Safely update an existing client

PUT /api/v1/Clients/{clientId} is not a patch operation. It replaces the complete redirectUris and postLogoutUris lists with the lists in the request. Omitting an existing URI removes it.

Use this sequence:

  1. Call GET from the owner tenant.
  2. Preserve every existing redirect and post-logout URI that must remain valid, together with the current display name, grants, and integration token.
  3. Add or change only the approved values.
  4. Send the complete lists in PUT.
  5. Call GET again and compare the full response.
  6. Test both the new URI and representative existing URIs.

Example update body:

{
"clientName": "Marketplace",
"grants": "AuthorizationCode",
"redirectUris": [
"http://localhost:3000/api/auth/callback/asol",
"https://alpha.avaplace.com/api/auth/callback/asol",
"https://alpha.avaplace.com/apps/mp/api/auth/callback/asol"
],
"postLogoutUris": [],
"allowedScopes": [],
"integrationToken": null
}

The null integration token in this Marketplace example is correct only when the preceding GET response also contains null. For another client, preserve the current value exactly.

Current implementation limitations:

  • PUT updates the display name, integration token, redirect URIs, post-logout URIs, and grant permissions.
  • It does not update description, identity management level, token lifetimes, domains, or allowed scopes.
  • Grant handling is additive. PUT adds permissions for the supplied grants but does not reliably remove permissions for grants previously assigned to the client. Treat grant removal or client-type conversion as a separate reviewed operation.

Tenant access administration

Grant access to another tenant

Switch the caller to the target tenant and call:

POST /api/asol/idp/api/v1/Clients/{clientId}/GrantTenantAccess

The target is always the current tenant from the caller's runtime context. A normal TenantAdmin can manage tenant relations only for a non-global client; a SpaceOwner can also administer global clients.

Remove tenant access

Switch to the target tenant and call:

DELETE /api/asol/idp/api/v1/Clients/{clientId}/RemoveTenantAccess

The relation is soft-deleted and can be reactivated by a later grant. The owner tenant cannot revoke its own relation.

GrantAccessToAllTenants

GrantAccessToAllTenants = true makes a client space-wide. It can obtain access in any tenant without individual ApplicationTenant relations and is visible across tenant contexts. Administration of a global client is consequently restricted primarily to SpaceOwner operations.

The public POST /api/v1/Clients model does not expose this flag, and PUT does not change it. Clients created through the public endpoint therefore have the flag set to false. A justified global client requires a reviewed seed, migration, or internal command executed with SpaceOwner authority. Do not use a direct database update as the normal registration procedure.

Reset a client secret

Send an empty JSON object:

POST /api/asol/idp/api/v1/Clients/{clientId}/ResetSecret
Content-Type: application/json

{}

The caller must be a SpaceOwner or act in the owner tenant with the required client-management authorization. The response contains the new clear-text secret once. The previous secret stops working immediately.

Use the AvaSupport Bruno collection

The maintained API client collection is in AvaSupport/Bruno. Open the Bruno directory itself as the collection root.

  1. Select the target environment, for example alpha.
  2. Verify ServerHost and ServiceTenantId. ServiceTenantId selects the owner or target tenant for the operation.
  3. Under IdentityProvider/v1/Clients, send an individual GET, POST, PUT, grant, revoke, or reset request.
  4. Replace the generated placeholder body completely. Do not leave placeholder values such as token lifetimes set to 0.
  5. Send mutating requests once and verify their result with GET.
  6. Do not run the complete Bruno collection against a shared environment.
  7. Do not commit one-off request bodies or secrets. Generated request files are overwritten when the collection is regenerated.

Troubleshooting

ResultCheck
400 Bad RequestDuplicate client id, empty or malformed URI, duplicate URI or scope, an invalid integration token, or an invalid front-channel logout callback (also revalidated when redirectUris changes, even if the callback field is omitted)
401 UnauthorizedMissing, expired, or invalid access token
403 ForbiddenMissing IDM right, wrong tenant context, or failed owner/SpaceOwner check
404 Not FoundClient does not exist in the caller's visible scope, or the caller is in a non-owner tenant
invalid_redirect_uri during sign-inRuntime callback does not exactly match one registered redirect URI

Final checklist

  • The clientId is unique and consistent across stages.
  • The request is executed in the approved owner tenant.
  • Grants match the real application flow.
  • Redirect URIs are exact, absolute, stage-specific, and minimal.
  • Post-logout URIs are actual end-session destinations, not copied callback URIs.
  • Requested scopes are documented and tested even though the current allowedScopes field is not persisted.
  • Tenant access is intentionally selected and tested.
  • Confidential secrets are stored once in a secret store; public-client secrets are discarded.
  • A GET verification and an integration test have succeeded before rollout to the next stage.

Source history

This document consolidates and supersedes the Confluence procedure Založení servisního účtu pro autentizaci služba vs služba (b2b). The Confluence page is retained for history; this repository document is authoritative when the two sources differ.