Class: AuthRocket::Session

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

Constant Summary collapse

JWKS_MUTEX =
Mutex.new

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#tokenObject (readonly)

readonly



11
12
13
# File 'lib/authrocket/session.rb', line 11

def token
  @token
end

Class Method Details

.from_token(token, options = {}) ⇒ Object

options - :algo - one of HS256, RS256 (default: auto-detect based on :jwt_key)

- :within - (in seconds) Maximum time since the token was (re)issued
- credentials: {jwt_key: StringOrKey} - used to verify the token

returns Session or nil

Raises:

  • (Error)


23
24
25
26
27
28
29
30
31
32
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
# File 'lib/authrocket/session.rb', line 23

def self.from_token(token, options={})
  if lr_url = options.dig(:credentials, :loginrocket_url) || credentials[:loginrocket_url]
    lr_url = lr_url.dup
    lr_url.concat '/' unless lr_url.ends_with?('/')
    lr_url.concat 'connect/jwks'
  end
  secret = options.dig(:credentials, :jwt_key) || credentials[:jwt_key]
  if secret.is_a?(String) && secret.length > 256
    unless secret.starts_with?('-----BEGIN ')
      secret = "-----BEGIN PUBLIC KEY-----\n#{secret}\n-----END PUBLIC KEY-----"
    end
    secret = OpenSSL::PKey.read secret
  end
  algo = options[:algo]
  algo ||= 'RS256' if secret.is_a?(OpenSSL::PKey::RSA)
  algo ||= 'HS256' if secret

  jwks_eligible = algo.in?([nil, 'RS256']) && secret.blank? && lr_url

  raise Error, "Missing jwt_key; set LOGINROCKET_URL, AUTHROCKET_JWT_KEY, or pass in credentials: {loginrocket_url: ...} or {jwt_key: ...}" if secret.blank? && !jwks_eligible
  return if token.blank?

  base_params = {token: token, within: options[:within], local_creds: options[:credentials]}
  if jwks_eligible
    kid = JSON.parse(JWT::Base64.url_decode(token.split('.')[0]))['kid'] rescue nil
    return if kid.blank?

    load_jwk_set(lr_url) unless @_jwks[kid]
    if key_set = @_jwks[kid]
      parse_jwt **key_set, **base_params
    end
  else
    parse_jwt secret: secret, algo: algo, **base_params
  end
end

.load_jwk_set(uri) ⇒ Object

private



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/authrocket/session.rb', line 123

def self.load_jwk_set(uri)
  JWKS_MUTEX.synchronize do
    path = URI.parse(uri).path
    headers = build_headers({}, {})
    rest_opts = {
      connect_timeout: 8,
      headers: headers,
      method: :get,
      path: path,
      read_timeout: 15,
      url: uri,
      write_timeout: 15,
    }
    response = execute_request(rest_opts)
    parsed = parse_response(response)
      # => {data: json, errors: errors, metadata: metadata}
    parsed[:data][:keys].each do |h|
      crt = "-----BEGIN PUBLIC KEY-----\n#{h['x5c'][0]}\n-----END PUBLIC KEY-----"
      @_jwks[h['kid']] = {secret: OpenSSL::PKey.read(crt), algo: h['alg']}
    end
  end
  @_jwks
end

.parse_jwt(token:, secret:, algo:, within:, local_creds: nil) ⇒ Object

private returns Session or nil



61
62
63
64
65
66
67
68
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/authrocket/session.rb', line 61

def self.parse_jwt(token:, secret:, algo:, within:, local_creds: nil)
  opts = {
    algorithm: algo,
    leeway: 5,
    iss: "https://authrocket.com",
    verify_iss: true,
  }

  jwt, _ = JWT.decode token, secret, true, opts

  if within
    # this ensures token was created recently
    # :iat is set to Time.now every time a token is created by the AR api
    return if jwt['iat'] < Time.now.to_i - within
  end

  user = User.new({
      id: jwt['sub'],
      realm_id: jwt['rid'],
      username: jwt['preferred_username'],
      first_name: jwt['given_name'],
      last_name: jwt['family_name'],
      name: jwt['name'],
      email: jwt['email'],
      email_verification: jwt['email_verified'] ? 'verified' : 'none',
      reference: jwt['ref'],
      custom: jwt['cs'],
      memberships: jwt['orgs'] && jwt['orgs'].map do |m|
        Membership.new({
          id: m['mid'],
          permissions: m['perm'],
          selected: m['selected'],
          user_id: jwt['sub'],
          org_id: m['oid'],
          org: Org.new({
            id: m['oid'],
            realm_id: jwt['rid'],
            name: m['name'],
            reference: m['ref'],
            custom: m['cs'],
          }, local_creds),
        }, local_creds)
      end,
    }, local_creds)
  session = new({
      id: jwt['sid'],
      created_at: jwt['iat'],
      expires_at: jwt['exp'],
      token: token,
      user_id: jwt['sub'],
      user: user
    }, local_creds)

  session
rescue JWT::DecodeError
  nil
end

Instance Method Details

#request_dataObject



14
15
16
# File 'lib/authrocket/session.rb', line 14

def request_data
  self[:request]
end