ruby / workos
We use WorkOS for SSO
and AuthKit
for our MCP server.
The workos gem is one of the few SDKs we depend on,
which is at odds with my thin HTTP client preference,
but auth is a security boundary worth treating carefully.
Boot
Configure the gem once at boot:
require "workos"
WorkOS.configure do |config|
config.api_key = ENV.fetch("WORKOS_API_KEY")
config.client_id = ENV.fetch("WORKOS_CLIENT_ID")
config.timeout = 5
end
Fork safety
Puma forks workers from a preloaded master.
The WorkOS client holds open sockets to api.workos.com.
Sharing sockets across forked workers causes errors,
so reset the client in each worker on boot:
Puma::Configuration.new do |c|
c.app APP
c.workers ENV.fetch("WEB_CONCURRENCY").to_i
c.preload_app!
c.before_worker_boot do
DB.reset_pool!
WorkOS.reset_client
end
end
Login
The login handler builds an authorization URL, stores a CSRF state token in the session, and renders a page with a "Login" button:
class Login < PublicHandler
def handle
state = SecureRandom.hex(16)
@session[:oauth_state] = state
workos_auth_url = WorkOS.client.sso.get_authorization_url(
organization: ENV.fetch("WORKOS_ORGANIZATION"),
redirect_uri: "#{base_url}/sso",
state: state
)
page "sso/login", workos_auth_url: workos_auth_url
end
end
get_authorization_url builds a string. No HTTP request.
Callback
The callback verifies the state parameter, exchanges the code for a profile, looks up the user by email, and remembers them via an encrypted cookie:
class Callback < PublicHandler
def handle
state = @session.delete(:oauth_state)
if params["state"].nil? || params["state"] != state
flash_next(:error, "Forbidden")
return redirect_to("/login")
end
if params["code"].nil?
flash_next(:error, "Forbidden")
return redirect_to("/login")
end
profile = WorkOS.client.sso.get_profile_and_token(
code: params["code"]
).profile
user = Users::ByEmail.new(db).call(profile.email)
if user == {}
flash_next(:error, "Forbidden")
return redirect_to("/login")
end
remember user["remember_token"]
redirect_to(safe_return_to)
end
end
Users::ByEmail queries WHERE active = true,
so deactivated users can't log in.
Audit logs
Successful logins write to WorkOS audit logs. The call is best-effort: a failure shouldn't block the login.
begin
WorkOS.client.audit_logs.create_event(
organization_id: ENV.fetch("WORKOS_ORGANIZATION"),
event: {
action: "user_logged_in",
occurred_at: Time.now.utc.iso8601,
actor: {id: user["id"].to_s, type: "user", name: user["name"]},
targets: [{id: user["id"].to_s, type: "user"}],
context: {location: @req.ip, user_agent: @req.user_agent}
}
)
rescue WorkOS::Error => err
Sentry.capture_message("WorkOS::AuditLogs.create_event #{err}")
end
Open redirect guard
After login, redirect to a return_to path stored before login.
Only allow internal paths:
def valid_return_path?(path)
if path.nil?
return false
end
if !path.start_with?("/")
return false
end
if path.start_with?("//") # protocol-relative URL
return false
end
true
end
AuthKit bridge
The same Login and Callback handlers
also serve our MCP AuthKit flow.
When Claude starts OAuth with AuthKit,
AuthKit redirects to GET /login?external_auth_id=xxx.
If the user is already signed in, complete the flow immediately:
if current_user["id"] && valid_external_auth_id
return complete_authkit_flow(external_auth_id, {
id: current_user["id"].to_s,
email: current_user["email"]
})
end
Otherwise stash external_auth_id in the session and run normal SSO.
The callback detects it and completes AuthKit instead of normal login.
The completion call is plain HTTP, not the SDK, since the gem doesn't cover this endpoint:
resp = HTTP
.auth("Bearer #{ENV.fetch("WORKOS_API_KEY")}")
.post("https://api.workos.com/authkit/oauth2/complete", json: {
external_auth_id: external_auth_id,
user: user_payload
})
body = JSON.parse(resp.body.to_s)
redirect_uri = body["redirect_uri"]
uri = URI.parse(redirect_uri.to_s)
if uri.scheme != "https" || !uri.host&.end_with?(".workos.com", ".authkit.app")
Sentry.capture_message("AuthKit redirect_uri rejected", extra: {
redirect_uri: redirect_uri
})
flash_next(:error, "MCP authentication error. Please try again.")
return redirect_to("/login")
end
[303, {"Location" => redirect_uri}, []]
The host check on redirect_uri is defense in depth against open redirects.
Audit logs fire on normal SSO logins but not on the AuthKit path, since the AuthKit branches return before reaching the audit log call.