Keycloak · Arazzo Workflow

Keycloak Register a Confidential OIDC Client

Version 1.0.0

Register an OpenID Connect client in a realm, resolve its internal UUID, and retrieve its generated secret.

1 workflow 1 source API 1 provider
View Spec View on GitHub AuthenticationAuthorizationIdentity ManagementOAuthOpenID ConnectSecuritySSOArazzoWorkflows

Provider

keycloak

Workflows

register-oidc-client
Create a confidential OIDC client and retrieve its client secret.
Idempotently registers a confidential OpenID Connect client with the authorization code flow enabled, resolves its internal UUID, and returns the generated client secret.
6 steps inputs: clientId, clientName, description, realm, redirectUris, rootUrl, serviceAccountsEnabled, webOrigins outputs: clientId, clientSecret, clientUuid, registeredRedirectUris
1
confirmRealm
getRealm
Confirm the realm exists and is enabled before registering anything into it.
2
precheckClient
getClients
Filter the realm's clients by clientId to see whether this application is already registered, keeping the workflow idempotent.
3
createConfidentialClient
createClient
Register the client as confidential with the authorization code flow on and the implicit flow off. Keycloak generates the secret server-side and answers 201 with a Location header and no body, so the secret is fetched later rather than read from here.
4
resolveClientUuid
getClients
Exchange the clientId for Keycloak's internal client UUID. Both the created and the already-registered branch converge here, because the secret and detail endpoints are keyed on the UUID rather than the clientId.
5
loadClient
getClient
Read the stored client representation back to confirm the flags Keycloak actually persisted, which is the only way to catch a silently defaulted setting.
6
fetchClientSecret
getClientSecret
Retrieve the server-generated client secret. This is the credential the application needs to complete the authorization code exchange, and it is only ever available from this endpoint.

Source API Descriptions

Arazzo Workflow Specification

Raw ↑
arazzo: 1.0.1
info:
  title: Keycloak Register a Confidential OIDC Client
  summary: Register an OpenID Connect client in a realm, resolve its internal UUID, and retrieve its generated secret.
  description: >-
    The flow every integrator runs when standing up a new application against
    Keycloak. It turns on a trap worth naming: Keycloak addresses clients by an
    internal UUID that is not the clientId you chose, and the create call answers
    201 with only a Location header and no body. So the clientId has to be
    exchanged for the UUID through a filtered list call before the secret can be
    fetched at all. The workflow pre-checks for an existing registration, creates
    the confidential client when it is missing, converges both branches on the
    UUID lookup, and ends by reading back the client and its generated secret.
    Every step spells out its request inline so the flow can be read and executed
    without opening the underlying OpenAPI description.
  version: 1.0.0
sourceDescriptions:
- name: keycloakAdminApi
  url: ../openapi/keycloak-admin-rest-api-openapi.yml
  type: openapi
workflows:
- workflowId: register-oidc-client
  summary: Create a confidential OIDC client and retrieve its client secret.
  description: >-
    Idempotently registers a confidential OpenID Connect client with the
    authorization code flow enabled, resolves its internal UUID, and returns the
    generated client secret.
  inputs:
    type: object
    required:
    - realm
    - clientId
    - redirectUris
    properties:
      realm:
        type: string
        description: The name of the realm to register the client in.
      clientId:
        type: string
        description: The OAuth client identifier used in OIDC flows (e.g. "billing-web").
      clientName:
        type: string
        description: A human readable display name for the client.
      description:
        type: string
        description: A description of what the client is for.
      rootUrl:
        type: string
        description: The root URL of the application.
      redirectUris:
        type: array
        description: The list of valid redirect URIs for the authorization code flow.
        items:
          type: string
      webOrigins:
        type: array
        description: The list of allowed CORS origins for the client.
        items:
          type: string
      serviceAccountsEnabled:
        type: boolean
        description: >-
          When true the client also gets a service account for the client
          credentials grant.
  steps:
  - stepId: confirmRealm
    description: >-
      Confirm the realm exists and is enabled before registering anything into
      it.
    operationId: getRealm
    parameters:
    - name: realm
      in: path
      value: $inputs.realm
    successCriteria:
    - condition: $statusCode == 200
    outputs:
      realmName: $response.body#/realm
      realmEnabled: $response.body#/enabled
  - stepId: precheckClient
    description: >-
      Filter the realm's clients by clientId to see whether this application is
      already registered, keeping the workflow idempotent.
    operationId: getClients
    parameters:
    - name: realm
      in: path
      value: $inputs.realm
    - name: clientId
      in: query
      value: $inputs.clientId
    - name: max
      in: query
      value: 1
    successCriteria:
    - condition: $statusCode == 200
    onSuccess:
    - name: clientMissing
      type: goto
      stepId: createConfidentialClient
      criteria:
      - context: $response.body
        condition: $.length == 0
        type: jsonpath
    - name: clientAlreadyRegistered
      type: goto
      stepId: resolveClientUuid
      criteria:
      - context: $response.body
        condition: $.length > 0
        type: jsonpath
  - stepId: createConfidentialClient
    description: >-
      Register the client as confidential with the authorization code flow on and
      the implicit flow off. Keycloak generates the secret server-side and
      answers 201 with a Location header and no body, so the secret is fetched
      later rather than read from here.
    operationId: createClient
    parameters:
    - name: realm
      in: path
      value: $inputs.realm
    requestBody:
      contentType: application/json
      payload:
        clientId: $inputs.clientId
        name: $inputs.clientName
        description: $inputs.description
        enabled: true
        protocol: openid-connect
        publicClient: false
        clientAuthenticatorType: client-secret
        standardFlowEnabled: true
        implicitFlowEnabled: false
        directAccessGrantsEnabled: false
        serviceAccountsEnabled: $inputs.serviceAccountsEnabled
        rootUrl: $inputs.rootUrl
        redirectUris: $inputs.redirectUris
        webOrigins: $inputs.webOrigins
        fullScopeAllowed: false
    successCriteria:
    - condition: $statusCode == 201
    outputs:
      createdClientLocation: $response.header.Location
  - stepId: resolveClientUuid
    description: >-
      Exchange the clientId for Keycloak's internal client UUID. Both the created
      and the already-registered branch converge here, because the secret and
      detail endpoints are keyed on the UUID rather than the clientId.
    operationId: getClients
    parameters:
    - name: realm
      in: path
      value: $inputs.realm
    - name: clientId
      in: query
      value: $inputs.clientId
    - name: max
      in: query
      value: 1
    successCriteria:
    - condition: $statusCode == 200
    - context: $response.body
      condition: $.length > 0
      type: jsonpath
    outputs:
      clientUuid: $response.body#/0/id
      resolvedClientId: $response.body#/0/clientId
  - stepId: loadClient
    description: >-
      Read the stored client representation back to confirm the flags Keycloak
      actually persisted, which is the only way to catch a silently defaulted
      setting.
    operationId: getClient
    parameters:
    - name: realm
      in: path
      value: $inputs.realm
    - name: clientUuid
      in: path
      value: $steps.resolveClientUuid.outputs.clientUuid
    successCriteria:
    - condition: $statusCode == 200
    outputs:
      clientEnabled: $response.body#/enabled
      publicClient: $response.body#/publicClient
      standardFlowEnabled: $response.body#/standardFlowEnabled
      serviceAccountsEnabled: $response.body#/serviceAccountsEnabled
      registeredRedirectUris: $response.body#/redirectUris
  - stepId: fetchClientSecret
    description: >-
      Retrieve the server-generated client secret. This is the credential the
      application needs to complete the authorization code exchange, and it is
      only ever available from this endpoint.
    operationId: getClientSecret
    parameters:
    - name: realm
      in: path
      value: $inputs.realm
    - name: clientUuid
      in: path
      value: $steps.resolveClientUuid.outputs.clientUuid
    successCriteria:
    - condition: $statusCode == 200
    outputs:
      secretType: $response.body#/type
      clientSecret: $response.body#/value
  outputs:
    clientUuid: $steps.resolveClientUuid.outputs.clientUuid
    clientId: $steps.resolveClientUuid.outputs.resolvedClientId
    clientSecret: $steps.fetchClientSecret.outputs.clientSecret
    registeredRedirectUris: $steps.loadClient.outputs.registeredRedirectUris