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
| Requirement | Registration path |
|---|---|
Backend service with ClientCredentials and a message-broker account | Product catalogue OAuth tab or POST /api/v1/Process/ResetSecret/client/{clientId} |
Interactive application with AuthorizationCode | Direct Clients API described in this document |
| Application release that creates IDM rights, navigation, and an optional IDP client together | IDM 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.
| Information | Required decision |
|---|---|
| Application | Human-readable application name and owning team |
| Jira issue | Work item that authorizes the registration and records rollout evidence |
clientId | Unique, stable technical identifier; use the same identifier on every stage |
| Owner tenant | Tenant that owns and administers the client |
| Stages | Environments where the client must exist |
| Grants | Interactive, service-to-service, or another explicitly justified flow |
| Redirect URIs | Exact absolute callback URIs for every deployment shape on the selected stage |
| Post-logout URIs | Exact destinations used after IDP end-session; leave empty when the client does not use provider logout |
| Requested scopes | Scopes sent by the application in authorization or token requests |
| Tenant access | Owner tenant only, explicitly granted tenants, or justified space-wide access |
| Identity management level | 1 by default; use 2 only when IDM must not add TenantOwner to a service account |
| Secret consumer | For 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:
| Grant | Value | Purpose |
|---|---|---|
Password | 1 | User name and password flow retained for compatibility |
ClientCredentials | 2 | Non-interactive service-to-service authentication |
AuthorizationCode | 4 | Interactive sign-in with an authorization callback |
LoginBehalfOf | 8 | Advanced 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
redirectUriscontains exact OAuth/OIDC callback addresses. IDP requires absolute, well-formed values and rejects duplicates.postLogoutUriscontains exact destinations accepted after an IDP end-session request. An OAuth callback URI is not automatically a valid post-logout URI.domainsis derived fromredirectUrisin API responses. Althoughdomainanddomainsexist 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
| Operation | Method and path |
|---|---|
| Read one client | GET /api/v1/Clients/{clientId} |
| List visible clients | GET /api/v1/Clients |
| Create client | POST /api/v1/Clients |
| Update client | PUT /api/v1/Clients/{clientId} |
| Delete client | DELETE /api/v1/Clients/{clientId} |
| Reset secret | POST /api/v1/Clients/{clientId}/ResetSecret |
| Grant access to the current tenant | POST /api/v1/Clients/{clientId}/GrantTenantAccess |
| Remove access from the current tenant | DELETE /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 Foundmeans 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 OKmeans 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 Forbiddenmeans 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:
clientIdandclientName,- 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:
- Call GET from the owner tenant.
- Preserve every existing redirect and post-logout URI that must remain valid, together with the current display name, grants, and integration token.
- Add or change only the approved values.
- Send the complete lists in PUT.
- Call GET again and compare the full response.
- 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.
- Select the target environment, for example
alpha. - Verify
ServerHostandServiceTenantId.ServiceTenantIdselects the owner or target tenant for the operation. - Under
IdentityProvider/v1/Clients, send an individual GET, POST, PUT, grant, revoke, or reset request. - Replace the generated placeholder body completely. Do not leave placeholder values such as token lifetimes set to
0. - Send mutating requests once and verify their result with GET.
- Do not run the complete Bruno collection against a shared environment.
- Do not commit one-off request bodies or secrets. Generated request files are overwritten when the collection is regenerated.
Troubleshooting
| Result | Check |
|---|---|
400 Bad Request | Duplicate 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 Unauthorized | Missing, expired, or invalid access token |
403 Forbidden | Missing IDM right, wrong tenant context, or failed owner/SpaceOwner check |
404 Not Found | Client does not exist in the caller's visible scope, or the caller is in a non-owner tenant |
invalid_redirect_uri during sign-in | Runtime callback does not exactly match one registered redirect URI |
Final checklist
- The
clientIdis 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
allowedScopesfield 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.