Deprecated : Please move to the corresponding endpoints under OIDC Service v2. This service will be removed in the next major version of ZITADEL.
Overview
The BetaWebKeyServiceApi provides methods for managing web key pairs used to sign and verify OIDC tokens (ID tokens, access tokens).
Initialization
require 'zitadel/client'
client = Zitadel :: Client :: ApiClient . new
client. config . access_token = 'your_access_token'
webkey_service = Zitadel :: Client :: Api :: BetaWebKeyServiceApi . new (client)
Web key management typically requires instance administrator permissions.
Key Concepts
Web Key : A public/private key pair for signing OIDC tokens
Active Key : The key currently used to sign new tokens
Initial Key : Newly created key, published but not yet active
Inactive Key : Previously active key, can still verify old tokens
Key Methods
Key Creation
create_web_key - Generate new key pair
beta_web_key_service_create_web_key_request
Key generation parameters Key algorithm (RSA, ECDSA, EdDSA)
Algorithm-specific configuration
# Create RSA key (default: 2048 bits, SHA256)
request = Zitadel :: Client :: Models :: BetaWebKeyServiceCreateWebKeyRequest . new (
config: {
rsa: {
bits: 'RSA_BITS_2048' ,
hasher: 'RSA_HASHER_SHA256'
}
}
)
response = webkey_service. create_web_key (request)
puts "Key ID: #{ response. details . key_id } "
puts "State: #{ response. details . state } " # STATE_INITIAL
puts "Algorithm: #{ response. details . algorithm } "
Required Permission : iam.web_key.writeReturns : BetaWebKeyServiceCreateWebKeyResponseThe JWKs endpoint (/.well-known/openid-configuration) is cached. Wait for cache expiration (default 5min) before activating new keys.
Key Activation
activate_web_key - Switch to new signing key
beta_web_key_service_activate_web_key_request
Key activation request ID of the key to activate
request = Zitadel :: Client :: Models :: BetaWebKeyServiceActivateWebKeyRequest . new (
key_id: 'key_123456789'
)
response = webkey_service. activate_web_key (request)
puts "Key activated: #{ response. details . key_id } "
puts "New state: #{ response. details . state } " # STATE_ACTIVE
Only one key can be active at a time
Previously active key becomes inactive
Do not activate keys created within cache duration (5min default)
Ensure public key has propagated to clients before activation
Required Permission : iam.web_key.writeReturns : BetaWebKeyServiceActivateWebKeyResponse
Key Listing
list_web_keys - List all web keys
Returns all web keys and their current states. request = {}
response = webkey_service. list_web_keys (request)
response. web_keys . each do | key |
puts "Key ID: #{ key. key_id } "
puts " Algorithm: #{ key. algorithm } "
puts " State: #{ key. state } "
puts " Created: #{ key. creation_date } "
puts " Activated: #{ key. activation_date } "
end
Required Permission : iam.web_key.readReturns : BetaWebKeyServiceListWebKeysResponse
Key Deletion
delete_web_key - Delete inactive key
beta_web_key_service_delete_web_key_request
request = Zitadel :: Client :: Models :: BetaWebKeyServiceDeleteWebKeyRequest . new (
key_id: 'key_123456789'
)
response = webkey_service. delete_web_key (request)
puts "Key deleted at: #{ response. details . change_date } "
Only inactive keys can be deleted
Active keys must be deactivated first (by activating another key)
Tokens signed with deleted keys will be invalid
JWKs endpoint is cached - tokens may validate until cache expires
Required Permission : iam.web_key.deleteReturns : BetaWebKeyServiceDeleteWebKeyResponse
Key Algorithms
RSA Keys
config: {
rsa: {
bits: 'RSA_BITS_2048' , # or RSA_BITS_3072, RSA_BITS_4096
hasher: 'RSA_HASHER_SHA256' # or RSA_HASHER_SHA384, RSA_HASHER_SHA512
}
}
Recommended: RSA 2048 with SHA256 for most use cases
ECDSA Keys
config: {
ecdsa: {
curve: 'ECDSA_CURVE_P256' # or P384, P521
}
}
Best for: Performance-sensitive applications, smaller key size
EdDSA Keys
config: {
ed25519: {} # No additional config needed
}
Best for: Modern applications, excellent performance and security
Web Key Lifecycle
1. Key Creation
# Generate new key
request = Zitadel :: Client :: Models :: BetaWebKeyServiceCreateWebKeyRequest . new (
config: {
rsa: {
bits: 'RSA_BITS_2048' ,
hasher: 'RSA_HASHER_SHA256'
}
}
)
key = webkey_service. create_web_key (request)
puts "Created key: #{ key. details . key_id } (state: INITIAL)"
2. Wait for Cache
# Wait for JWKs cache to expire (default 5 minutes)
sleep ( 300 )
# Or check cache-control headers from JWKs endpoint
# GET https://your-instance.zitadel.cloud/.well-known/openid-configuration/jwks
3. Activate New Key
# Activate the new key
request = Zitadel :: Client :: Models :: BetaWebKeyServiceActivateWebKeyRequest . new (
key_id: key. details . key_id
)
webkey_service. activate_web_key (request)
puts "Key activated - now signing new tokens"
4. Monitor Old Key Usage
# Old key is now inactive but still validates existing tokens
# Monitor token expiration times
# Delete old key after all tokens have expired
5. Delete Old Key
# After token lifetime has passed
request = Zitadel :: Client :: Models :: BetaWebKeyServiceDeleteWebKeyRequest . new (
key_id: old_key_id
)
webkey_service. delete_web_key (request)
puts "Old key deleted"
Use Cases
Key Rotation
def rotate_signing_key
# 1. Create new key
puts "Creating new signing key..."
create_req = Zitadel :: Client :: Models :: BetaWebKeyServiceCreateWebKeyRequest . new (
config: {
rsa: {
bits: 'RSA_BITS_2048' ,
hasher: 'RSA_HASHER_SHA256'
}
}
)
new_key = webkey_service. create_web_key (create_req)
new_key_id = new_key. details . key_id
puts "New key created: #{ new_key_id } "
puts "Waiting for JWKs cache to expire (5 minutes)..."
# 2. Wait for cache expiration
sleep ( 300 )
# 3. Get current active key
list_req = {}
keys = webkey_service. list_web_keys (list_req)
active_key = keys. web_keys . find { | k | k. state == 'STATE_ACTIVE' }
# 4. Activate new key (deactivates old one)
puts "Activating new key..."
activate_req = Zitadel :: Client :: Models :: BetaWebKeyServiceActivateWebKeyRequest . new (
key_id: new_key_id
)
webkey_service. activate_web_key (activate_req)
puts "Key rotation complete!"
puts "Old key ( #{ active_key &. key_id } ) is now inactive"
puts "New key ( #{ new_key_id } ) is now active"
# 5. Schedule old key deletion
# Delete after token lifetime (e.g., 24 hours)
puts "Schedule deletion of old key after token expiration"
new_key_id
end
List Active and Inactive Keys
def list_keys_by_state
request = {}
response = webkey_service. list_web_keys (request)
keys = {
active: [],
inactive: [],
initial: []
}
response. web_keys . each do | key |
info = {
key_id: key. key_id ,
algorithm: key. algorithm ,
created: key. creation_date
}
case key. state
when 'STATE_ACTIVE'
keys[ :active ] << info
when 'STATE_INACTIVE'
keys[ :inactive ] << info
when 'STATE_INITIAL'
keys[ :initial ] << info
end
end
keys
end
# Usage
keys = list_keys_by_state
puts "Active keys: #{ keys[ :active ]. size } "
puts "Inactive keys: #{ keys[ :inactive ]. size } "
puts "Initial keys: #{ keys[ :initial ]. size } "
Cleanup Old Inactive Keys
def cleanup_old_keys ( max_age_days: 30 )
request = {}
response = webkey_service. list_web_keys (request)
cutoff_date = Time . now - (max_age_days * 24 * 60 * 60 )
response. web_keys . each do | key |
# Only delete inactive keys older than cutoff
if key. state == 'STATE_INACTIVE'
key_date = Time . parse (key. deactivation_date || key. creation_date )
if key_date < cutoff_date
delete_req = Zitadel :: Client :: Models :: BetaWebKeyServiceDeleteWebKeyRequest . new (
key_id: key. key_id
)
webkey_service. delete_web_key (delete_req)
puts "Deleted old key: #{ key. key_id } (deactivated: #{ key_date } )"
end
end
end
end
Verify Key State Before Token Generation
def verify_active_key_exists
request = {}
response = webkey_service. list_web_keys (request)
active_key = response. web_keys . find { | k | k. state == 'STATE_ACTIVE' }
if active_key
puts "Active signing key: #{ active_key. key_id } "
puts " Algorithm: #{ active_key. algorithm } "
puts " Activated: #{ active_key. activation_date } "
true
else
puts "ERROR: No active signing key found!"
puts "Tokens cannot be issued without an active key."
false
end
end
Automated Key Rotation
def schedule_key_rotation ( rotation_days: 90 )
request = {}
response = webkey_service. list_web_keys (request)
active_key = response. web_keys . find { | k | k. state == 'STATE_ACTIVE' }
if active_key
activation_date = Time . parse (active_key. activation_date )
rotation_date = activation_date + (rotation_days * 24 * 60 * 60 )
if Time . now >= rotation_date
puts "Active key is #{ (( Time . now - activation_date) / 86400 ). to_i } days old"
puts "Rotation needed! Creating new key..."
rotate_signing_key
else
days_until_rotation = ((rotation_date - Time . now ) / 86400 ). to_i
puts "Next rotation in #{ days_until_rotation } days"
end
else
puts "No active key - creating initial key..."
create_and_activate_initial_key
end
end
Key States
STATE_INITIAL
Newly created key
Published in JWKs endpoint
Not yet used for signing
Can be activated
STATE_ACTIVE
Currently signing all new tokens
Only one key can be active
Published in JWKs endpoint
Cannot be deleted
STATE_INACTIVE
Previously active key
Can still verify old tokens
Published in JWKs endpoint (for verification)
Can be deleted
Public Key Distribution
Public keys are automatically published to:
https://your-instance.zitadel.cloud/.well-known/openid-configuration/jwks
Applications use this endpoint to verify token signatures.
Cache Behavior
Response is cached (default: 5 minutes)
Cache-Control header indicates duration
Keys may not be immediately available after creation
Wait for cache expiration before activating new keys
Key Rotation Best Practices
Rotate keys every 90 days minimum
More frequent for high-security environments
Coordinate with token lifetime
Keep old keys for at least the token lifetime
Example: If tokens expire in 24 hours, keep old keys for 48+ hours
This allows existing tokens to remain valid
Create new key at least 5 minutes before activation
Allows public key to propagate to caches
Prevents validation failures
Monitor key age
Alert when rotation is due
Track token validation failures
Audit key creation/deletion
Algorithm Selection
RSA (Recommended)
config: {
rsa: {
bits: 'RSA_BITS_2048' ,
hasher: 'RSA_HASHER_SHA256'
}
}
Pros : Widely supported, industry standard
Cons : Larger key and signature size
Best for : General use, maximum compatibility
ECDSA
config: {
ecdsa: {
curve: 'ECDSA_CURVE_P256'
}
}
Pros : Smaller keys, good performance
Cons : Less universal support
Best for : Modern applications, mobile apps
EdDSA (Ed25519)
Pros : Excellent performance, strong security, small signatures
Cons : Requires modern JWT libraries
Best for : New applications, high-performance requirements
Security Considerations
Important Security Notes :
Never export or share private keys
Rotate keys regularly (every 90 days minimum)
Delete old keys after grace period
Monitor for unauthorized key operations
Use strongest algorithm your clients support
Maintain audit trail of key operations
Troubleshooting
Tokens Not Validating
# Check if active key exists
keys = webkey_service. list_web_keys ({})
active = keys. web_keys . find { | k | k. state == 'STATE_ACTIVE' }
if active. nil?
puts "No active key! Create and activate a key."
else
puts "Active key: #{ active. key_id } "
puts "Check if clients have cached old JWKs"
end
Cannot Delete Key
# Error: Key is active
# Solution: Activate another key first
# Create new key
new_key = webkey_service. create_web_key (...)
# Wait for cache
sleep ( 300 )
# Activate new key (deactivates old one)
webkey_service. activate_web_key (
key_id: new_key. details . key_id
)
# Now delete old key
webkey_service. delete_web_key (
key_id: old_key_id
)
Required Permissions
iam.web_key.read - List and view web keys
iam.web_key.write - Create and activate web keys
iam.web_key.delete - Delete web keys
Migration Guide
To migrate to OIDC Service v2:
Replace BetaWebKeyServiceApi with appropriate v2 endpoint
Review key management changes
Test token signing and validation
Update key rotation scripts
See Also