As part of an initiative to improve the handling of OAuth client credentials, the OAuth client secret will no longer be returned in API responses, so that this information is kept secret and less likely to be compromised.
This is part 2 of 2 customer-affecting changes being made to improve the handling of OAuth client credentials. Part 1 involves removing the client secret from the Admin UI so that it is only available in the UI at the time of creating an OAuth client or when a new secret is requested. It will no longer be visible in the UI subsequently but it can still be retrieved via API to lessen the impact for customers that weren’t prepared for this change.
Part 2 then involves removing the client secret from API responses to complete the OAuth client credential handling improvements. At this point, the client secret will no longer be visible in the Admin UI (post create/reset) and will not be returned in API responses.
Change Category
API
Change Context
This change is part of an initiative to improve the handling of OAuth client credentials, so that this information is kept secret and less likely to be compromised.
Change Impact (Updated Jan 29)
Once this change has been implemented, the OAuth Client Secret will only be available at the time of creating an OAuth client or when a new secret is requested. The Admin will need to copy and store the secret locally. It will no longer be possible to retrieve the client secret subsequently via either the Admin UI or via API GET requests.
Note that there is no change to the behavior for POST /api/v2/oauth/clients. The client secret will still be returned via API on client creation or client secret reset when using the POST command. However, this is the only time that the client secret is exposed.
This announcement relates only to the removal of the OAuth Client Secret from API responses. A separate announcement on the UI changes will be available shortly.
Date of Change
June 2025 (Deferred from Mar 24th to allow additional time) - Exact date to be confirmed closer to the time
Impacted APIs
GET /api/v2/oauth/clients/{clientId}
PUT /api/v2/oauth/clients/{clientId}
According to the post, the impacted APIs are only the GET and PUT so the POST to create the oauth client via api should be unaffected. Assuming that POST /api/v2/oauth/clients doesn't change.
I understand this change, and I appreciate the focus on security. I would also want the API to continue returning the OAuth Client Secret on creation, please.
On another note, the CX as Code for OAuth does not output the Client Secret even on creation. That may not be possible given that one would need to use an output to retrieve it, and we wouldn't be able to output that value on subsequent calls. That being said, I prefer to include all GC resources in my CX as Code Terraform templates needed for a solution, including the OAuth clients. If I can't get the Client Secret except at the time of creation, then that limits my use CX as Code for OAuth.
Completing my thought from my previous post... I think I integrate a call to this webservice into my Terraform templates. As long as it will continue to return the secret, of course...
I have updated the announcement to reflect the fact that POST /api/v2/oauth/clients doesn't change. The client secret will be returned via api on create and client secret reset. However, these are the only times that the client secret is exposed.
Apologies for the confusion on this.
@davidmurray thanks for this information. Is it possible to share the full information about the reasons why this change is being done. It seems to me from your "Change Context" that its about making the OAuth credentials secure, and that raised a question then, what else is not secure in Genesys Cloud if Genesys thinks having the OAuth will be more secure outside Genesys.
Why can't this be controlled by elevated roles/permissions, rather than a one time code.
What I have started to do now is save the secret somewhere else, while it is still visible, which in itself is less secure thank before.
Storing client secrets in a SaaS product such as Genesys Cloud is not a good security practice, so I won't attempt to defend that. We continue to work with the Genesys Security team to identify potential vulnerabilities and attack vectors and we put plans in place to remediate them. This initiative is as a result of those conversations. Once we have completed this initiative, the client secret will no longer be stored in the product. Instead we will store the hash of the secret and use that hash to match against the supplied secret.
You may already have a secrets management system in place, in which case this is just another secret to be added there. If you don't already have a formalized secrets management system, have a look at articles such as the OWASP Cheat Sheet Series (Secrets Management - OWASP Cheat Sheet Series) for ideas on the best mechanisms to use.
I'm very happy to see this change. It always scared me that someone with the correct permissions could view the full OAuth credentials and potentially use them externally, or deconstruct a data action and build something malicious to extract sensitive data.
If this change goes through, it break our terraform setup that uses integration_actions that require credentials, as there is no way to get the clientId and clientSecret from the oauth_client resource, we use the GET call as a workaround.
That is pretty clever the way you did that with the data source. I personally never thought of that.
I would look at grabbing your client ID and secret and storing them in a secret's vault (e.g. Hashicorp vault, AWS key storage, Infisical). It's more aligned with best practices and keeps your secrets for all your integrations in one place.
Thanks,
John Carnell
Director, Developer Engagement
We really dont want to manually copy/paste credentials for every credentials that we have, this is because we balance an X amount of oauth client over 42 different actions on ACC and PRD. It will be a very tedious task to do this for our setup. Especially because we want to be able to balance this at any time due to current platform limitations (rate limits).
If the terraform can return the credentials on creation it would be fine for us, but that is currently not possible.
I would dump all of your oauth credentials and secrets to a file before the API is shutdown and then look at put them in a third-party secret vault.
This is an OWasp best practice and there is really nothing else we can do since we are eliminating this from te Genesys Cloud API.
While the API still returns the secrets you can use the genesys cloud cli to do this: gc oauth clients list -a. You will need to use an OAuth client or user id with the right level of permissions.
Most of these secret values have an API to programmatically add client and id secrets to a vault.
Thanks,
John Carnell
Director, Developer Engagement
Here is an example code i generated using an LLM. So please test this against a DEV hashicorp environment. It is without warranties or guarantees. Never accept random swords from ladies in a lake and always test code from the internet in a Sandbox environment
It reads the output from the gc oauth clients list -a and then calls the HashiCorp rest API.
It probably works, but it should give you an idea of how to do this task.
import json
import sys
import os
import requests
from typing import Dict, List
from urllib3.exceptions import InsecureRequestWarning
from requests.exceptions import RequestException
# Suppress only the single warning from urllib3 needed.
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
class VaultClient:
def __init__(self):
# Get Vault configuration from environment variables
self.vault_addr = os.getenv('VAULT_ADDR')
self.vault_token = os.getenv('VAULT_TOKEN')
if not self.vault_addr or not self.vault_token:
raise ValueError("VAULT_ADDR and VAULT_TOKEN environment variables must be set")
self.headers = {
'X-Vault-Token': self.vault_token,
'Content-Type': 'application/json'
}
def write_secret(self, path: str, data: Dict) -> bool:
"""
Write a secret to Vault
"""
url = f"{self.vault_addr}/v1/secret/data/oauth-clients/{path}"
# Format data according to KV2 secret engine requirements
payload = {
"data": data
}
try:
response = requests.post(
url,
headers=self.headers,
json=payload,
verify=False # Note: In production, you should verify SSL certificates
)
response.raise_for_status()
return True
except RequestException as e:
print(f"Error writing to Vault: {str(e)}")
return False
def parse_oauth_credentials(json_file: str) -> List[Dict]:
"""
Parse OAuth credentials from JSON file and format for Vault
Only includes CLIENT-CREDENTIALS grant type entries
"""
try:
with open(json_file, 'r') as f:
data = json.load(f)
vault_entries = []
for entry in data:
if entry.get("authorizedGrantType") == "CLIENT-CREDENTIALS":
vault_entry = {
"client_id": entry.get("id", ""),
"client_secret": entry.get("secret", ""),
"description": entry.get("description", entry.get("name", "No description provided"))
}
vault_entries.append(vault_entry)
return vault_entries
except FileNotFoundError:
print(f"Error: File {json_file} not found")
sys.exit(1)
except json.JSONDecodeError:
print(f"Error: File {json_file} is not valid JSON")
sys.exit(1)
def write_to_vault(entries: List[Dict]) -> None:
"""
Write entries directly to HashiCorp Vault
"""
if not entries:
print("No CLIENT-CREDENTIALS entries found in the input file.")
return
vault_client = VaultClient()
success_count = 0
failure_count = 0
for entry in entries:
client_id = entry['client_id']
try:
if vault_client.write_secret(client_id, entry):
print(f"Successfully wrote secret for client ID: {client_id}")
success_count += 1
else:
print(f"Failed to write secret for client ID: {client_id}")
failure_count += 1
except Exception as e:
print(f"Error processing client ID {client_id}: {str(e)}")
failure_count += 1
print(f"\nSummary:")
print(f"Successfully wrote {success_count} secrets")
print(f"Failed to write {failure_count} secrets")
def main():
# Verify environment variables
required_env_vars = ['VAULT_ADDR', 'VAULT_TOKEN']
missing_vars = [var for var in required_env_vars if not os.getenv(var)]
if missing_vars:
print(f"Missing required environment variables: {', '.join(missing_vars)}")
print("Please set the following environment variables:")
print("export VAULT_ADDR='https://your-vault-address:8200'")
print("export VAULT_TOKEN='your-vault-token'")
sys.exit(1)
json_file = "data.json" # You can modify this to accept command line arguments
vault_entries = parse_oauth_credentials(json_file)
write_to_vault(vault_entries)
if __name__ == "__main__":
main()
The main problem we will be facing is that when this change has been applied, there is no way to manage the oauth clients in terraform anymore. We create our oauth clients using terraform and use the credentials on resources depending on it (integrations).
When using terraform to create the oauth client, the credentials are not retreived or stored anywhere. so our current workaround is to use the api call to retreive them. I fully understand why this change is happening and that it will go through eventually, breaking the workaround.
When the api call is altered so that you can not retreive the credentials after creating, how would this work with the terraform example that i gave you earlier? (create oauth client and use its credentials with an integration)
Let me chat with my team. I am wondering if on creation you can kind bind the secret to an output variable so you could at least access it in your script. I will need to chat with my team and get their thoughts on this.