Sessions in Zitadel represent authenticated user states and can be used for authentication across multiple applications. This guide covers creating, managing, and validating sessions.
Prerequisites
- Zitadel Ruby SDK installed and configured
- Authentication configured
- Required permissions:
session.read, session.write, session.delete
Understanding Sessions
A session in Zitadel:
- Represents an authenticated user state
- Can include multiple authentication factors
- Returns a session token that can be used as an OAuth2 access token
- Can have metadata and expiration settings
- Can be used to authenticate OIDC and SAML requests
Creating Sessions
Create a Basic Session
Create a session with a user check:
require 'zitadel-client'
client = Zitadel::Client::Zitadel.with_access_token(
"https://example.zitadel.cloud",
"your_access_token"
)
request = Zitadel::Client::SessionServiceCreateSessionRequest.new(
checks: {
user: {
login_name: "[email protected]"
}
},
lifetime: "18000s" # 5 hours
)
begin
response = client.sessions.create_session(request)
puts "Session created!"
puts " Session ID: #{response.session_id}"
puts " Session Token: #{response.session_token}"
puts " Expires in: #{response.details.lifetime}"
rescue Zitadel::Client::ApiError => e
puts "Error creating session: #{e.message}"
end
Store the session token securely. It can be used to authenticate API requests and should be treated like a password.
Create Session with Password Check
request = Zitadel::Client::SessionServiceCreateSessionRequest.new(
checks: {
user: {
login_name: "[email protected]"
},
password: {
password: "user_password_here"
}
},
lifetime: "3600s" # 1 hour
)
response = client.sessions.create_session(request)
puts "Authenticated session created: #{response.session_id}"
require 'base64'
metadata = {
ip_address: "192.168.1.1",
device: "mobile",
app_version: "1.2.3"
}.to_json
request = Zitadel::Client::SessionServiceCreateSessionRequest.new(
checks: {
user: {
login_name: "[email protected]"
}
},
metadata: {
"client_info" => Base64.strict_encode64(metadata)
},
lifetime: "7200s"
)
response = client.sessions.create_session(request)
puts "Session with metadata created: #{response.session_id}"
Session metadata values must be base64 encoded.
Retrieving Sessions
Get Session by ID
request = Zitadel::Client::SessionServiceGetSessionRequest.new(
session_id: "session_id_here",
session_token: "session_token_here" # Optional if you have permission
)
response = client.sessions.get_session(request)
puts "Session Details:"
puts " ID: #{response.session.id}"
puts " User ID: #{response.session.factors.user.id}"
puts " Login Name: #{response.session.factors.user.login_name}"
puts " Created: #{response.session.creation_date}"
puts " Expires: #{response.session.expiration_date}"
if response.session.metadata
puts " Metadata:"
response.session.metadata.each do |key, value|
decoded = Base64.strict_decode64(value)
puts " #{key}: #{decoded}"
end
end
You can retrieve a session without the session token if:
- You created the session
- You’re requesting your own session
- You have
session.read permission
List Sessions
request = Zitadel::Client::SessionServiceListSessionsRequest.new(
queries: []
)
response = client.sessions.list_sessions(request)
response.sessions.each do |session|
puts "Session: #{session.id}"
puts " User: #{session.factors.user.login_name}"
puts " Created: #{session.creation_date}"
puts " Expires: #{session.expiration_date || 'Never'}"
end
Search Sessions by User
request = Zitadel::Client::SessionServiceListSessionsRequest.new(
queries: [
{
user_id_query: {
user_id: "user_id_here"
}
}
]
)
response = client.sessions.list_sessions(request)
puts "Found #{response.sessions.length} sessions for user"
Updating Sessions
Update Session Lifetime
request = Zitadel::Client::SessionServiceSetSessionRequest.new(
session_id: "session_id_here",
session_token: "current_session_token",
lifetime: "36000s" # 10 hours
)
response = client.sessions.set_session(request)
puts "Session updated!"
puts "New session token: #{response.session_token}"
Updating a session invalidates the previous token. You must use the new token returned in the response.
metadata = { last_activity: Time.now.to_s }.to_json
request = Zitadel::Client::SessionServiceSetSessionRequest.new(
session_id: "session_id_here",
session_token: "current_session_token",
metadata: {
"activity" => Base64.strict_encode64(metadata)
}
)
response = client.sessions.set_session(request)
puts "Metadata updated, new token: #{response.session_token}"
Add Additional Authentication Checks
request = Zitadel::Client::SessionServiceSetSessionRequest.new(
session_id: "session_id_here",
session_token: "current_session_token",
checks: {
totp: {
code: "123456" # OTP code from authenticator app
}
}
)
response = client.sessions.set_session(request)
puts "MFA verified, new token: #{response.session_token}"
Deleting Sessions
Delete a Single Session
request = Zitadel::Client::SessionServiceDeleteSessionRequest.new(
session_id: "session_id_here",
session_token: "session_token_here" # Optional if you have permission
)
response = client.sessions.delete_session(request)
puts "Session terminated"
You can delete your own sessions without the session.delete permission. The session token is sufficient.
Delete All User Sessions (Logout Everywhere)
# List all sessions for a user
list_request = Zitadel::Client::SessionServiceListSessionsRequest.new(
queries: [
{
user_id_query: {
user_id: "user_id_here"
}
}
]
)
list_response = client.sessions.list_sessions(list_request)
# Delete each session
list_response.sessions.each do |session|
delete_request = Zitadel::Client::SessionServiceDeleteSessionRequest.new(
session_id: session.id
)
client.sessions.delete_session(delete_request)
puts "Deleted session: #{session.id}"
end
puts "All sessions terminated"
Complete Workflow Examples
User Login with Session
require 'zitadel-client'
client = Zitadel::Client::Zitadel.with_private_key(
"https://example.zitadel.cloud",
"path/to/service-account.json"
)
def login(client, username, password)
# Create session with password verification
request = Zitadel::Client::SessionServiceCreateSessionRequest.new(
checks: {
user: {
login_name: username
},
password: {
password: password
}
},
lifetime: "3600s", # 1 hour
metadata: {
"login_time" => Base64.strict_encode64(Time.now.to_s)
}
)
begin
response = client.sessions.create_session(request)
{
success: true,
session_id: response.session_id,
session_token: response.session_token,
message: "Login successful"
}
rescue Zitadel::Client::ApiError => e
{
success: false,
message: "Login failed: #{e.message}"
}
end
end
# Usage
result = login(client, "[email protected]", "password123")
if result[:success]
puts "Logged in successfully!"
puts "Session Token: #{result[:session_token]}"
else
puts result[:message]
end
Session Validation and Refresh
def validate_and_refresh_session(client, session_id, session_token)
begin
# Get current session details
get_request = Zitadel::Client::SessionServiceGetSessionRequest.new(
session_id: session_id,
session_token: session_token
)
session = client.sessions.get_session(get_request).session
# Check if session is expired
if session.expiration_date
expiration = Time.parse(session.expiration_date)
if expiration < Time.now
return { valid: false, message: "Session expired" }
end
end
# Refresh session if it expires in less than 5 minutes
if session.expiration_date
expiration = Time.parse(session.expiration_date)
if expiration < Time.now + 300 # 5 minutes
set_request = Zitadel::Client::SessionServiceSetSessionRequest.new(
session_id: session_id,
session_token: session_token,
lifetime: "3600s" # Extend by 1 hour
)
response = client.sessions.set_session(set_request)
return {
valid: true,
refreshed: true,
new_token: response.session_token,
message: "Session refreshed"
}
end
end
{ valid: true, refreshed: false, message: "Session valid" }
rescue Zitadel::Client::ApiError => e
{ valid: false, message: "Session invalid: #{e.message}" }
end
end
# Usage
result = validate_and_refresh_session(client, session_id, session_token)
if result[:valid]
puts result[:message]
if result[:refreshed]
puts "New token: #{result[:new_token]}"
# Update stored token
end
else
puts "Session invalid: #{result[:message]}"
# Redirect to login
end
Session-Based API Authentication
# Use session token as OAuth2 access token
require 'net/http'
require 'json'
def call_api_with_session(session_token, endpoint)
uri = URI(endpoint)
request = Net::HTTP::Get.new(uri)
request['Authorization'] = "Bearer #{session_token}"
request['Content-Type'] = 'application/json'
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
if response.is_a?(Net::HTTPSuccess)
JSON.parse(response.body)
else
{ error: "API call failed: #{response.code} #{response.message}" }
end
end
# Create session
create_request = Zitadel::Client::SessionServiceCreateSessionRequest.new(
checks: {
user: { login_name: "[email protected]" },
password: { password: "secure_password" }
},
lifetime: "3600s"
)
session_response = client.sessions.create_session(create_request)
# Use session token to call Zitadel API
user_data = call_api_with_session(
session_response.session_token,
"https://example.zitadel.cloud/v2/users/me"
)
puts "User data: #{user_data}"
Best Practices
Set appropriate lifetimes
Use shorter lifetimes (1-2 hours) for sensitive applications and longer lifetimes for less critical apps.
Store session tokens securely (HttpOnly cookies for web, secure storage for mobile).
Refresh sessions before they expire to provide seamless user experience.
Always validate sessions before performing sensitive operations.
Delete sessions on logout and implement automatic cleanup of expired sessions.
Store relevant context (IP address, device info) but avoid sensitive data.
Implement proper error handling for expired or invalid sessions.
Security Considerations
- Never expose session tokens in URLs or logs
- Use HTTPS for all session-related communications
- Implement CSRF protection for web applications
- Rotate session tokens after privilege escalation
- Delete sessions immediately on logout
Next Steps