ClientApplication $rotate-secret | Medplum

On this page

The $rotate-secret operation securely rotates client application credentials with zero downtime. It uses a two-phase rotation process: first moving the current secret to a "retiring" state while generating a new primary secret, then later removing the retiring secret—ensuring that active clients aren't immediately locked out during credential updates.

Regular credential rotation is a security best practice that limits the impact of compromised credentials and meets compliance requirements for many healthcare security frameworks.

Use Cases

Invoke the $rotate-secret operation

[baseUrl]/ClientApplication/[id]/$rotate-secret

Parameters

Name Type Description Required
secret string Rotate the primary secret, generating a new one and placing the old one into retiringSecret No
retiringSecret string Rotate the retiring secret, removing it from use No

Mutually Exclusive Parameters

One and only one of the secret and retiringSecret parameters must be provided, and must match the corresponding value in the ClientApplication.

Output

The operation returns the ClientApplication resource with the updated secret value.

Examples

Fully rotate client secret:

// First, rotate the initial secret

const rotatedClient: ClientApplication = await medplum.post(

medplum.fhirUrl('ClientApplication', clientApplication.id, '$rotate-secret'),

{
    resourceType: 'Parameters',
    parameter: [{ name: 'secret', valueString: clientApplication.secret }],
  }
);

console.log('Client secret rotated; new secret is:', rotatedClient.secret);
console.log('Previous secret is still available for use:', rotatedClient.retiringSecret);

// At this point, existing application instances should be updated to use the new secret
// Once all use of the old (retiring) secret is resolved, rotate it out of service

await medplum.post(medplum.fhirUrl('ClientApplication', clientApplication.id, '$rotate-secret'), {
  resourceType: 'Parameters',
  parameter: [{ name: 'retiringSecret', valueString: rotatedClient.retiringSecret }],
});

// Now only the newly generated secret value will be valid