Class: WorkOS::Session

Inherits:
Object
  • Object
show all
Defined in:
lib/workos/session.rb

Overview

Wraps a sealed session cookie for authentication, refresh, and logout. Constructed by WorkOS::SessionManager#load; not intended for direct instantiation.

Examples:

Authenticate and refresh

session = client.session_manager.load(seal_data: cookie, cookie_password: pw)
result = session.authenticate
if result.is_a?(SessionManager::AuthError) && result.reason == SessionManager::EXPIRED_JWT
  refresh = session.refresh
end

Build a logout URL

url = session.get_logout_url(return_to: "https://app.example.com")

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(manager, seal_data:, cookie_password:) ⇒ Session

Returns a new instance of Session.

Raises:

  • (ArgumentError)


24
25
26
27
28
29
30
# File 'lib/workos/session.rb', line 24

def initialize(manager, seal_data:, cookie_password:)
  raise ArgumentError, "cookie_password is required" if cookie_password.nil? || cookie_password.empty?
  @manager = manager
  @client = manager.client
  @seal_data = seal_data
  @cookie_password = cookie_password
end

Instance Attribute Details

Returns the value of attribute cookie_password.



32
33
34
# File 'lib/workos/session.rb', line 32

def cookie_password
  @cookie_password
end

#seal_dataObject (readonly)

Returns the value of attribute seal_data.



32
33
34
# File 'lib/workos/session.rb', line 32

def seal_data
  @seal_data
end

Instance Method Details

#authenticate(include_expired: false, &claim_extractor) ⇒ Hash

Authenticates the user based on the session data

Parameters:

  • include_expired (Boolean) (defaults to: false)

    If true, returns decoded token data even when expired (default: false)

  • block (Proc)

    Optional block to call to extract additional claims from the decoded JWT

Returns:

  • (Hash)

    A hash containing the authentication response and a reason if the authentication failed



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/workos/session.rb', line 38

def authenticate(include_expired: false, &claim_extractor)
  return SessionManager::AuthError.new(authenticated: false, reason: SessionManager::NO_SESSION_COOKIE_PROVIDED) if @seal_data.nil? || @seal_data.empty?

  session = begin
    @manager.unseal_data(@seal_data, @cookie_password)
  rescue ArgumentError, OpenSSL::Cipher::CipherError
    return SessionManager::AuthError.new(authenticated: false, reason: SessionManager::INVALID_SESSION_COOKIE)
  end
  return SessionManager::AuthError.new(authenticated: false, reason: SessionManager::INVALID_SESSION_COOKIE) unless session.is_a?(Hash) && session["access_token"]

  decoded = begin
    @manager.decode_jwt(session["access_token"], verify_expiration: !include_expired)
  rescue JWT::ExpiredSignature
    return SessionManager::AuthError.new(authenticated: false, reason: SessionManager::EXPIRED_JWT)
  rescue JWT::IncorrectAlgorithm
    return SessionManager::AuthError.new(authenticated: false, reason: SessionManager::INVALID_JWT_ALGORITHM)
  rescue JWT::VerificationError
    return SessionManager::AuthError.new(authenticated: false, reason: SessionManager::INVALID_JWT_SIGNATURE)
  rescue JWT::DecodeError
    return SessionManager::AuthError.new(authenticated: false, reason: SessionManager::INVALID_JWT)
  end

  is_expired = decoded["exp"] && decoded["exp"] < Time.now.to_i

  SessionManager::AuthSuccess.new(
    authenticated: !is_expired,
    reason: is_expired ? SessionManager::EXPIRED_JWT : nil,
    session_id: decoded["sid"],
    organization_id: decoded["org_id"],
    role: decoded["role"],
    roles: decoded["roles"],
    permissions: decoded["permissions"],
    entitlements: decoded["entitlements"],
    user: session["user"],
    impersonator: session["impersonator"],
    feature_flags: decoded["feature_flags"],
    custom_claims: claim_extractor&.call(decoded)
  )
end

#get_logout_url(return_to: nil) ⇒ Object

Build the WorkOS session-logout URL for the currently authenticated session. Requires #authenticate to succeed (so we have the session_id).

Raises:



124
125
126
127
128
129
130
131
132
133
# File 'lib/workos/session.rb', line 124

def get_logout_url(return_to: nil)
  result = authenticate
  raise WorkOS::Error.new(message: "Failed to extract session ID for logout URL: #{result.reason}") if result.is_a?(SessionManager::AuthError)
  base = @client.base_url
  params = {"session_id" => result.session_id}
  params["return_to"] = return_to if return_to
  uri = URI.join(base, "/user_management/sessions/logout")
  uri.query = URI.encode_www_form(params)
  uri.to_s
end

#refresh(organization_id: nil, cookie_password: nil) ⇒ Object



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/workos/session.rb', line 78

def refresh(organization_id: nil, cookie_password: nil)
  effective_password = cookie_password || @cookie_password

  session = begin
    @manager.unseal_data(@seal_data, effective_password)
  rescue ArgumentError, OpenSSL::Cipher::CipherError
    return SessionManager::RefreshError.new(authenticated: false, reason: SessionManager::INVALID_SESSION_COOKIE)
  end
  return SessionManager::RefreshError.new(authenticated: false, reason: SessionManager::INVALID_SESSION_COOKIE) unless session.is_a?(Hash) && session["refresh_token"]

  # Uses auth: true (Bearer token) to match authenticate_with_refresh_token.
  # client_id is included in the body as required by the OAuth2 token exchange.
  body = {
    "grant_type" => "refresh_token",
    "client_id" => @client.client_id,
    "refresh_token" => session["refresh_token"],
    "session" => {"seal_session" => true, "cookie_password" => effective_password}
  }
  body["organization_id"] = organization_id if organization_id

  response = @client.request(method: :post, path: "/user_management/authenticate", auth: true, body: body)
  auth_response = JSON.parse(response.body)
  sealed = auth_response["sealed_session"].to_s
  @seal_data = sealed
  @cookie_password = effective_password

  decoded = @manager.decode_jwt(auth_response["access_token"])
  SessionManager::RefreshSuccess.new(
    authenticated: true,
    sealed_session: sealed,
    session_id: decoded["sid"],
    organization_id: decoded["org_id"],
    role: decoded["role"],
    roles: decoded["roles"],
    permissions: decoded["permissions"],
    entitlements: decoded["entitlements"],
    user: auth_response["user"],
    impersonator: auth_response["impersonator"],
    feature_flags: decoded["feature_flags"]
  )
rescue WorkOS::AuthenticationError, WorkOS::InvalidRequestError => e
  SessionManager::RefreshError.new(authenticated: false, reason: e.message)
end