Generate a new consumer secret for a partner. Optionally keep the old secret valid for a grace period to enable zero-downtime rotation in distributed deployments.
When to use: When a consumer secret is compromised, an employee with access leaves, or as part of regular secret rotation policy.
# From the registration result
partner_id = registration.partner.id
# Or look up by consumer key
lookup = await provider.get_partner_by_consumer_key("ck_live_a1b2c3d4...")
if lookup.is_ok:
partner_id = lookup.unwrap().id
# Option A: Immediate rotation (old secret invalidated)
result = await provider.rotate_consumer_secret(partner_id=partner_id)
# Option B: Grace period rotation (old secret valid for 1 hour)
result = await provider.rotate_consumer_secret(
partner_id=partner_id,
grace_period_seconds=3600, # 0-604800 (max 7 days)
)
# Without grace period: old secret fails immediately
# With grace period: old secret works until grace period expires
old_result = await provider.validate_partner_signature(
consumer_key=registration.consumer_key,
signature=old_signature, # Computed with old secret
payload=payload,
timestamp=timestamp,
)
print(f"Old secret valid: {old_result.is_ok}")
# True if within grace period, False if no grace period or expired
Consumer Key: ck_live_a1b2c3d4e5f6...
New Secret: cs_live_new_secret_here...
Old secret valid: False
Consumer Key: ck_live_a1b2c3d4e5f6...
New Secret: cs_live_new_secret_here...
Old secret valid: True (within 1-hour grace period)
"""GL IAM Rotate Consumer Secret Example."""
import asyncio
import hashlib
import hmac
from datetime import datetime, timezone
from gl_iam.core.crypto_config import CryptoConfig, EncryptionAlgorithm
from gl_iam.core.types.sso import SSOPartnerCreate
from gl_iam.providers.postgresql import (
PostgreSQLPartnerRegistryProvider,
PostgreSQLConfig,
)
DATABASE_URL = "postgresql+asyncpg://postgres:postgres@localhost:5432/gliam"
SECRET_KEY = "your-secret-key-min-32-characters-long" # For JWT signing
# Generate with: python -c "import secrets, base64; print(base64.urlsafe_b64encode(secrets.token_bytes(32)).decode())"
ENCRYPTION_KEY = "<YOUR_ENCRYPTION_KEY>"
def compute_signature(consumer_key: str, consumer_secret: str, payload: str) -> tuple[str, str]:
"""Compute HMAC-SHA256 signature (partner side)."""
timestamp = datetime.now(timezone.utc).isoformat()
signing_string = f"{timestamp}|{consumer_key}|{payload}"
signature = hmac.new(
consumer_secret.encode(),
signing_string.encode(),
hashlib.sha256,
).hexdigest()
return signature, timestamp
async def main():
# Setup
config = PostgreSQLConfig(
database_url=DATABASE_URL,
secret_key=SECRET_KEY,
crypto_config=CryptoConfig(
encryption_keys={1: ENCRYPTION_KEY},
encryption_algorithm=EncryptionAlgorithm.AES_256_GCM,
),
)
provider = PostgreSQLPartnerRegistryProvider(config)
# Register partner
reg_result = await provider.register_partner(
SSOPartnerCreate(partner_name="Acme Corp", org_id="default")
)
registration = reg_result.unwrap()
partner_id = registration.partner.id
consumer_key = registration.consumer_key
old_secret = registration.consumer_secret
print(f"Registered: {registration.partner.partner_name}")
print(f" Consumer Key: {consumer_key}")
print(f" Original Secret: {old_secret[:16]}...")
# Verify old secret works
payload = '{"email": "alice@example.com"}'
sig, ts = compute_signature(consumer_key, old_secret, payload)
result = await provider.validate_partner_signature(
consumer_key=consumer_key, signature=sig, payload=payload, timestamp=ts,
)
print(f"\nOld secret works: {result.is_ok}")
# Rotate the secret with a 1-hour grace period
rotate_result = await provider.rotate_consumer_secret(
partner_id=partner_id,
grace_period_seconds=3600, # Old secret valid for 1 hour
)
new_registration = rotate_result.unwrap()
new_secret = new_registration.consumer_secret
print(f"\nRotated (with 1-hour grace period)!")
print(f" Consumer Key: {new_registration.consumer_key}") # Same
print(f" New Secret: {new_secret[:16]}...")
# Verify new secret works
sig, ts = compute_signature(consumer_key, new_secret, payload)
result = await provider.validate_partner_signature(
consumer_key=consumer_key, signature=sig, payload=payload, timestamp=ts,
)
print(f"\nNew secret works: {result.is_ok}")
# Old secret still works during grace period
sig, ts = compute_signature(consumer_key, old_secret, payload)
result = await provider.validate_partner_signature(
consumer_key=consumer_key, signature=sig, payload=payload, timestamp=ts,
)
print(f"Old secret works (grace period): {result.is_ok}") # True
# For immediate invalidation, omit grace_period_seconds:
# await provider.rotate_consumer_secret(partner_id=partner_id)
await provider.close()
if __name__ == "__main__":
asyncio.run(main())
uv run rotate_consumer_secret.py
Registered: Acme Corp
Consumer Key: ck_live_a1b2c3d4e5f6...
Original Secret: cs_live_x9y8z7w6...
Old secret works: True
Rotated (with 1-hour grace period)!
Consumer Key: ck_live_a1b2c3d4e5f6...
New Secret: cs_live_new_a1b2c3d4...
New secret works: True
Old secret works (grace period): True