Keycloak · Arazzo Workflow

Keycloak Rotate a Client Secret

Version 1.0.0

Resolve a client by clientId, capture the outgoing secret, regenerate it, and verify the new value.

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

Provider

keycloak

Workflows

rotate-client-secret
Regenerate a confidential client's secret and confirm the new value.
Resolves a client by its clientId, records the current secret, regenerates it, and verifies that the stored secret matches the newly issued value.
5 steps inputs: clientId, realm outputs: clientId, clientUuid, newSecret, previousSecret
1
resolveClient
getClients
Exchange the clientId for the internal client UUID. The secret endpoints are keyed on the UUID, and this lookup is the only way to get it.
2
loadClient
getClient
Read the client representation to confirm it is confidential before rotating. A public client has no secret to rotate, so this step is the guard that keeps the flow from doing something meaningless.
3
captureOutgoingSecret
getClientSecret
Record the secret that is about to be retired, so the operator can tell which value was replaced and confirm the rotation actually changed it.
4
regenerateSecret
regenerateClientSecret
Issue a new client secret. This takes effect immediately and invalidates the previous value, so the application configuration must be updated with the new secret before its next token request.
5
verifyRotation
getClientSecret
Read the stored secret back and assert it no longer matches the value captured before rotation, proving the new credential is the live one.

Source API Descriptions

Arazzo Workflow Specification

Raw ↑
arazzo: 1.0.1
info:
  title: Keycloak Rotate a Client Secret
  summary: Resolve a client by clientId, capture the outgoing secret, regenerate it, and verify the new value.
  description: >-
    Credential rotation as an executable artifact. The workflow resolves the
    internal client UUID from the clientId, captures the outgoing secret so the
    operator can recognise which value is being retired and roll back the
    deployment side if needed, regenerates the secret, and then reads it back to
    confirm the new value is live. Rotation is immediate and unrecoverable on the
    Keycloak side: the moment regeneration succeeds the previous secret stops
    authenticating, so the new value must reach the application's configuration
    before the next token request. 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: rotate-client-secret
  summary: Regenerate a confidential client's secret and confirm the new value.
  description: >-
    Resolves a client by its clientId, records the current secret, regenerates
    it, and verifies that the stored secret matches the newly issued value.
  inputs:
    type: object
    required:
    - realm
    - clientId
    properties:
      realm:
        type: string
        description: The name of the realm the client is registered in.
      clientId:
        type: string
        description: The OAuth client identifier whose secret should be rotated.
  steps:
  - stepId: resolveClient
    description: >-
      Exchange the clientId for the internal client UUID. The secret endpoints
      are keyed on the UUID, and this lookup is the only way to get it.
    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 client representation to confirm it is confidential before
      rotating. A public client has no secret to rotate, so this step is the
      guard that keeps the flow from doing something meaningless.
    operationId: getClient
    parameters:
    - name: realm
      in: path
      value: $inputs.realm
    - name: clientUuid
      in: path
      value: $steps.resolveClient.outputs.clientUuid
    successCriteria:
    - condition: $statusCode == 200
    - context: $response.body
      condition: $.publicClient == false
      type: jsonpath
    outputs:
      clientEnabled: $response.body#/enabled
      publicClient: $response.body#/publicClient
      clientAuthenticatorType: $response.body#/clientAuthenticatorType
  - stepId: captureOutgoingSecret
    description: >-
      Record the secret that is about to be retired, so the operator can tell
      which value was replaced and confirm the rotation actually changed it.
    operationId: getClientSecret
    parameters:
    - name: realm
      in: path
      value: $inputs.realm
    - name: clientUuid
      in: path
      value: $steps.resolveClient.outputs.clientUuid
    successCriteria:
    - condition: $statusCode == 200
    outputs:
      previousSecret: $response.body#/value
  - stepId: regenerateSecret
    description: >-
      Issue a new client secret. This takes effect immediately and invalidates
      the previous value, so the application configuration must be updated with
      the new secret before its next token request.
    operationId: regenerateClientSecret
    parameters:
    - name: realm
      in: path
      value: $inputs.realm
    - name: clientUuid
      in: path
      value: $steps.resolveClient.outputs.clientUuid
    successCriteria:
    - condition: $statusCode == 200
    outputs:
      newSecretType: $response.body#/type
      newSecret: $response.body#/value
  - stepId: verifyRotation
    description: >-
      Read the stored secret back and assert it no longer matches the value
      captured before rotation, proving the new credential is the live one.
    operationId: getClientSecret
    parameters:
    - name: realm
      in: path
      value: $inputs.realm
    - name: clientUuid
      in: path
      value: $steps.resolveClient.outputs.clientUuid
    successCriteria:
    - condition: $statusCode == 200
    - condition: $response.body#/value != $steps.captureOutgoingSecret.outputs.previousSecret
    outputs:
      confirmedSecret: $response.body#/value
  outputs:
    clientUuid: $steps.resolveClient.outputs.clientUuid
    clientId: $steps.resolveClient.outputs.resolvedClientId
    previousSecret: $steps.captureOutgoingSecret.outputs.previousSecret
    newSecret: $steps.verifyRotation.outputs.confirmedSecret