class AuthRocket::Session

Constants

JWKS_MUTEX

Attributes

token[R]

Public Class Methods

from_token(token, options={}) click to toggle source

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

# 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) click to toggle source

private

# 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) click to toggle source

private returns Session or nil

# 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

Public Instance Methods

request_data() click to toggle source
# File lib/authrocket/session.rb, line 14
def request_data
  self[:request]
end