Class: WorkOS::Session

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

Overview

The Session class provides helper methods for working with WorkOS sessions This class is not meant to be instantiated in a user space, and is instantiated internally but exposed.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(user_management:, client_id:, session_data:, cookie_password:) ⇒ Session

Returns a new instance of Session.

Raises:

  • (ArgumentError)


17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/workos/session.rb', line 17

def initialize(user_management:, client_id:, session_data:, cookie_password:)
  raise ArgumentError, 'cookiePassword is required' if cookie_password.nil? || cookie_password.empty?

  @user_management = user_management
  @cookie_password = cookie_password
  @session_data = session_data
  @client_id = client_id

  @jwks = Cache.fetch("jwks_#{client_id}", expires_in: 5 * 60) do
    create_remote_jwk_set(URI(@user_management.get_jwks_url(client_id)))
  end
  @jwks_algorithms = @jwks.map { |key| key[:alg] }.compact.uniq
end

Instance Attribute Details

#client_idObject

Returns the value of attribute client_id.



15
16
17
# File 'lib/workos/session.rb', line 15

def client_id
  @client_id
end

Returns the value of attribute cookie_password.



15
16
17
# File 'lib/workos/session.rb', line 15

def cookie_password
  @cookie_password
end

#jwksObject

Returns the value of attribute jwks.



15
16
17
# File 'lib/workos/session.rb', line 15

def jwks
  @jwks
end

#jwks_algorithmsObject

Returns the value of attribute jwks_algorithms.



15
16
17
# File 'lib/workos/session.rb', line 15

def jwks_algorithms
  @jwks_algorithms
end

#session_dataObject

Returns the value of attribute session_data.



15
16
17
# File 'lib/workos/session.rb', line 15

def session_data
  @session_data
end

#user_managementObject

Returns the value of attribute user_management.



15
16
17
# File 'lib/workos/session.rb', line 15

def user_management
  @user_management
end

Class Method Details

.seal_data(data, key) ⇒ String

Encrypts and seals data using AES-256-GCM

Parameters:

  • data (Hash)

    The data to seal

  • key (String)

    The key to use for encryption

Returns:

  • (String)

    The sealed data



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

def self.seal_data(data, key)
  iv = SecureRandom.random_bytes(12)

  encrypted_data = Encryptor.encrypt(
    value: JSON.generate(data),
    key: key,
    iv: iv,
    algorithm: 'aes-256-gcm',
  )
  Base64.encode64(iv + encrypted_data) # Combine IV with encrypted data and encode as base64
end

.unseal_data(sealed_data, key) ⇒ Hash

Decrypts and unseals data using AES-256-GCM

Parameters:

  • sealed_data (String)

    The sealed data to unseal

  • key (String)

    The key to use for decryption

Returns:

  • (Hash)

    The unsealed data



138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/workos/session.rb', line 138

def self.unseal_data(sealed_data, key)
  decoded_data = Base64.decode64(sealed_data)
  iv = decoded_data[0..11] # Extract the IV (first 12 bytes)
  encrypted_data = decoded_data[12..-1] # Extract the encrypted data

  decrypted_data = Encryptor.decrypt(
    value: encrypted_data,
    key: key,
    iv: iv,
    algorithm: 'aes-256-gcm',
  )

  JSON.parse(decrypted_data, symbolize_names: true) # Parse the decrypted JSON string back to original data
end

Instance Method Details

#authenticateHash

Authenticates the user based on the session data

Returns:

  • (Hash)

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



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/workos/session.rb', line 33

def authenticate
  return { authenticated: false, reason: 'NO_SESSION_COOKIE_PROVIDED' } if @session_data.nil?

  begin
    session = Session.unseal_data(@session_data, @cookie_password)
  rescue StandardError
    return { authenticated: false, reason: 'INVALID_SESSION_COOKIE' }
  end

  return { authenticated: false, reason: 'INVALID_SESSION_COOKIE' } unless session[:access_token]
  return { authenticated: false, reason: 'INVALID_JWT' } unless is_valid_jwt(session[:access_token])

  decoded = JWT.decode(session[:access_token], nil, true, algorithms: @jwks_algorithms, jwks: @jwks).first

  {
    authenticated: true,
    session_id: decoded['sid'],
    organization_id: decoded['org_id'],
    role: decoded['role'],
    permissions: decoded['permissions'],
    entitlements: decoded['entitlements'],
    user: session[:user],
    impersonator: session[:impersonator],
    reason: nil,
  }
end

#get_logout_url(return_to: nil) ⇒ String

Returns a URL to redirect the user to for logging out

Parameters:

  • return_to (String) (defaults to: nil)

    The URL to redirect the user to after logging out

Returns:

  • (String)

    The URL to redirect the user to for logging out



108
109
110
111
112
113
114
115
116
# File 'lib/workos/session.rb', line 108

def get_logout_url(return_to: nil)
  auth_response = authenticate

  unless auth_response[:authenticated]
    raise "Failed to extract session ID for logout URL: #{auth_response[:reason]}"
  end

  @user_management.get_logout_url(session_id: auth_response[:session_id], return_to: return_to)
end

#refresh(options = nil) ⇒ Hash

Refreshes the session data using the refresh token stored in the session data and a reason if the refresh failed rubocop:disable Metrics/AbcSize rubocop:disable Metrics/CyclomaticComplexity rubocop:disable Metrics/PerceivedComplexity

Parameters:

  • options (Hash) (defaults to: nil)

    Options for refreshing the session

Options Hash (options):

  • :cookie_password (String)

    The password to use for unsealing the session data

  • :organization_id (String)

    The organization ID to use for refreshing the session

Returns:

  • (Hash)

    A hash containing a new sealed session, the authentication response,



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/workos/session.rb', line 69

def refresh(options = nil)
  cookie_password = options.nil? || options[:cookie_password].nil? ? @cookie_password : options[:cookie_password]

  begin
    session = Session.unseal_data(@session_data, cookie_password)
  rescue StandardError
    return { authenticated: false, reason: 'INVALID_SESSION_COOKIE' }
  end

  return { authenticated: false, reason: 'INVALID_SESSION_COOKIE' } unless session[:refresh_token] && session[:user]

  begin
    auth_response = @user_management.authenticate_with_refresh_token(
      client_id: @client_id,
      refresh_token: session[:refresh_token],
      organization_id: options.nil? || options[:organization_id].nil? ? nil : options[:organization_id],
      session: { seal_session: true, cookie_password: cookie_password },
    )

    @session_data = auth_response.sealed_session
    @cookie_password = cookie_password

    {
      authenticated: true,
      sealed_session: auth_response.sealed_session,
      session: auth_response,
      reason: nil,
    }
  rescue StandardError => e
    { authenticated: false, reason: e.message }
  end
end