Skip to content

Conversation

@SamuelDev
Copy link

@SamuelDev SamuelDev commented Jan 23, 2026

Related issue(s)

Implementing fosite's client secret rotation for hydra clients

Checklist

  • I have read the contributing guidelines.
  • I have referenced an issue containing the design document if my change
    introduces a new feature.
  • I am following the
    contributing code guidelines.
  • I have read the security policy.
  • I confirm that this pull request does not address a security
    vulnerability. If this pull request addresses a security vulnerability, I
    confirm that I got the approval (please contact
    security@ory.sh) from the maintainers to push
    the changes.
  • I have added tests that prove my fix is effective or that my feature
    works.
  • I have added or changed the documentation.

Further Comments

Client secret rotation has been implemented in fosite for nearly 5 years but has lacked support in hydra. My team needs this feature, and I am contributing it back to the source project for everyone.

In addition to the Go tests included, I asked AI to generate a test script for these changes:

#!/bin/bash

ADMIN_URL="http://127.0.0.1:4445"
PUBLIC_URL="http://127.0.0.1:4444"

echo "=== OAuth 2.0 Client Secret Rotation Demo ==="
echo ""

# Create client
echo "1. Creating client..."
CLIENT=$(curl -s -X POST "$ADMIN_URL/admin/clients" \
  -H "Content-Type: application/json" \
  -d '{"client_name":"Test Client","grant_types":["client_credentials"],"token_endpoint_auth_method":"client_secret_basic"}')

CLIENT_ID=$(echo $CLIENT | jq -r '.client_id')
SECRET1=$(echo $CLIENT | jq -r '.client_secret')
echo "   Client ID: $CLIENT_ID"
echo "   Secret: $SECRET1"
echo ""

# Test with original secret
echo "2. Testing authentication with original secret..."
RESULT=$(curl -s -X POST "$PUBLIC_URL/oauth2/token" \
  -u "$CLIENT_ID:$SECRET1" \
  -d "grant_type=client_credentials")
if echo "$RESULT" | grep -q "access_token"; then
  echo "   ✓ SUCCESS - Got access token"
else
  echo "   ✗ FAILED"
fi
echo ""

# Rotate secret
echo "3. Rotating secret..."
ROTATED=$(curl -s -X POST "$ADMIN_URL/admin/clients/$CLIENT_ID/secret/rotate")
SECRET2=$(echo $ROTATED | jq -r '.client_secret')
echo "   New Secret: $SECRET2"
echo ""

# Test with NEW secret
echo "4. Testing with NEW secret..."
RESULT=$(curl -s -X POST "$PUBLIC_URL/oauth2/token" \
  -u "$CLIENT_ID:$SECRET2" \
  -d "grant_type=client_credentials")
if echo "$RESULT" | grep -q "access_token"; then
  echo "   ✓ SUCCESS - New secret works"
else
  echo "   ✗ FAILED"
fi
echo ""

# Test with OLD secret (THIS IS THE KEY TEST!)
echo "5. Testing with OLD secret (should still work!)..."
RESULT=$(curl -s -X POST "$PUBLIC_URL/oauth2/token" \
  -u "$CLIENT_ID:$SECRET1" \
  -d "grant_type=client_credentials")
if echo "$RESULT" | grep -q "access_token"; then
  echo "   ✓ SUCCESS - Old secret still works! SECRET ROTATION IS WORKING!"
else
  echo "   ✗ FAILED - Old secret doesn't work"
fi
echo ""

# Check rotated secrets
echo "6. Checking client details..."
CLIENT_INFO=$(curl -s "$ADMIN_URL/admin/clients/$CLIENT_ID")
ROTATED_SECRETS=$(echo $CLIENT_INFO | jq -r '.rotated_secrets')
echo "   Rotated secrets stored: $ROTATED_SECRETS"
echo ""

# Clean up rotated secrets
echo "7. Cleaning up rotated secrets..."
curl -s -X DELETE "$ADMIN_URL/admin/clients/$CLIENT_ID/secret/rotate" > /dev/null
echo "   ✓ Rotated secrets removed"
echo ""

# Test OLD secret after cleanup (should fail)
echo "8. Testing OLD secret after cleanup (should fail)..."
RESULT=$(curl -s -X POST "$PUBLIC_URL/oauth2/token" \
  -u "$CLIENT_ID:$SECRET1" \
  -d "grant_type=client_credentials")
if echo "$RESULT" | grep -q "error"; then
  echo "   ✓ SUCCESS - Old secret correctly rejected"
else
  echo "   ✗ FAILED - Old secret still works (unexpected)"
fi
echo ""

# Test NEW secret after cleanup (should work)
echo "9. Testing NEW secret after cleanup (should still work)..."
RESULT=$(curl -s -X POST "$PUBLIC_URL/oauth2/token" \
  -u "$CLIENT_ID:$SECRET2" \
  -d "grant_type=client_credentials")
if echo "$RESULT" | grep -q "access_token"; then
  echo "   ✓ SUCCESS - New secret still works"
else
  echo "   ✗ FAILED"
fi
echo ""

echo "=== Demo Complete ==="
echo ""
echo "Summary: Secret rotation allows zero-downtime credential updates."
echo "Both old and new secrets work during the rotation period, then"
echo "old secrets can be safely removed after all services are updated."

@SamuelDev SamuelDev requested review from a team and aeneasr as code owners January 23, 2026 16:45
@CLAassistant
Copy link

CLAassistant commented Jan 23, 2026

CLA assistant check
All committers have signed the CLA.

@SamuelDev SamuelDev changed the title Implement client secret rotation feat: Implement client secret rotation Jan 23, 2026
@SamuelDev SamuelDev force-pushed the hydra-client-secret-rotation branch from 1757cbd to 1703655 Compare January 23, 2026 17:19
@SamuelDev
Copy link
Author

docs: ory/docs#2424

@SamuelDev
Copy link
Author

At this point I will leave any CI/test failures as is and allow conversation to begin on whether or not this change is desired in its current shape.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants