Zero-downtime client secret rotation with Cloud Foundry UAA

It’s important that your Cloud Foundry Oauth2 Clients are able to continually interact with the UAA, for example to verify the validity of opaque tokens. But if the client secret needs to be rotated, it’s hard to get the timing just right. After all — there may be some delay until the new secret can be provided to the client application, or require a client application restart.

Fortunately, the UAA allows a client to have multiple client secrets at one time, to facilitate cutting over the client applications from the old secret to the new secret.

To demonstrate, run the UAA:

$ git clone https://github.com/cloudfoundry/uaa.git
$ cd uaa
$ ./gradlew run
# wait for the following line to appear
Press Ctrl-C to stop the container...

Then get a sample token to pass to the /introspect endpoint. This will use the bootstrapped client apps described in oauth-clients.xml.

$ curl 'http://localhost:8080/uaa/oauth/token' -i -X POST \
-H 'Accept: application/json' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'client_id=app&client_secret=appclientsecret&grant_type=client_credentials&token_format=opaque'
# find the access_token in the response
# introspect the token
$ curl 'http://localhost:8080/uaa/introspect' -i -u 'app:appclientsecret' \
-X POST \
-d 'token=<access_token>'
# should return JSON which includes "active": true

As it happens we got a client_credentials token for client app, and then used app:appclientsecret to auth ourselves to the /introspect endpoint as well.

Let’s add a new client secret for client app. To do this, we’ll need a higher level of permissions (such as clients.write). Let’s get a client_credentials token for client admin. Note that it’s not possible for a client to change its own secret without specific authorities.

$ curl 'http://localhost:8080/uaa/oauth/token' -i -X POST \
-H 'Accept: application/json' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'client_id=admin&client_secret=adminsecret&grant_type=client_credentials&token_format=opaque'
# find the access_token in the response$ curl 'http://localhost:8080/uaa/oauth/clients/app/secret' -i -X PUT \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <admin client access_token>' \
-H 'Content-Type: application/json' \
-d '{
"clientId": "app",
"secret": "new_secret",
"changeMode": "ADD"
}'
# Should return 200 {"status":"ok","message":"Secret is added"}

Now you can use either secret appclientsecret or new_secret to auth to the introspect endpoint.

$ curl 'http://localhost:8080/uaa/introspect' -i -u 'app:appclientsecret' -X POST \
-d 'token=<access_token>'
$ curl 'http://localhost:8080/uaa/introspect' -i -u 'app:new_secret' -X POST \
-d 'token=<access_token>'

Then, when you’re ready, delete the old client secret.

$ curl 'http://localhost:8080/uaa/oauth/clients/app/secret' -i -X PUT \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <admin client access_token>' \
-H 'Content-Type: application/json' \
-d '{
"clientId": "app",
"changeMode": "DELETE"
}'

Now secret appclientsecret no longer works!

For more information about Cloud Foundry, visit https://www.cloudfoundry.org/

For more information about Cloud Foundry UAA, visit

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store