module Okta::Jwt

Constants

JWKS_CACHE

keys are cached under their kid value

VERSION

Attributes

auth_server_id[RW]
issuer[RW]
logger[RW]

Public Instance Methods

client(issuer) click to toggle source

init client

# File lib/okta/jwt.rb, line 92
def client(issuer)
  Faraday.new(url: issuer) do |f|
    f.use Faraday::Adapter::NetHttp
    f.headers['Accept'] = 'application/json'
  end
end
configure!(issuer:, logger: nil) click to toggle source

configure the client for signing in

# File lib/okta/jwt.rb, line 21
def configure!(issuer:, logger: nil)
  @issuer         = issuer
  @auth_server_id = issuer.split('/').last
end
get_jwk(header, payload) click to toggle source

extract public key from metadata's jwks_uri using kid

# File lib/okta/jwt.rb, line 62
def get_jwk(header, payload)
  kid = header['kid']

  # cache hit
  return JWKS_CACHE[kid] if JWKS_CACHE[kid]
  
  # fetch jwk
  logger.info("[Okta::Jwt] Fetching public key: kid => #{kid} ...") if logger
  jwks_response = client(payload['iss']).get do |req|
    req.url get_metadata(payload)['jwks_uri']
  end
  jwk = JSON.parse(jwks_response.body)['keys'].find do |key|
    key.dig('kid') == kid
  end
  
  # cache and return the key
  jwk.tap{JWKS_CACHE[kid] = jwk}
end
get_metadata(payload) click to toggle source

fetch client metadata using cid/aud

# File lib/okta/jwt.rb, line 82
def get_metadata(payload)
  auth_server_id    = payload['iss'].split('/').last # iss: "https://<org>.oktapreview.com/oauth2/<auth_server_id>"
  client_id         = payload['cid']
  metadata_response = client(payload['iss']).get do |req|
    req.url "/oauth2/#{auth_server_id}/.well-known/oauth-authorization-server?client_id=#{client_id}"
  end
  JSON.parse(metadata_response.body)
end
sign_in_client(client_id:, client_secret:, scope:) click to toggle source

sign in client to get access_token

# File lib/okta/jwt.rb, line 37
def sign_in_client(client_id:, client_secret:, scope:)
  client(issuer).post do |req|
    req.url "/oauth2/#{auth_server_id}/v1/token"
    req.headers['Content-Type']   = 'application/x-www-form-urlencoded'
    req.headers['Authorization']  = 'Basic: ' + Base64.strict_encode64("#{client_id}:#{client_secret}")
    req.body = URI.encode_www_form scope: scope, grant_type: 'client_credentials'
  end
end
sign_in_user(username:, password:, client_id:, client_secret:, scope: 'openid') click to toggle source

sign in user to get tokens

# File lib/okta/jwt.rb, line 27
def sign_in_user(username:, password:, client_id:, client_secret:, scope: 'openid')
  client(issuer).post do |req|
    req.url "/oauth2/#{auth_server_id}/v1/token"
    req.headers['Content-Type']   = 'application/x-www-form-urlencoded'
    req.headers['Authorization']  = 'Basic: ' + Base64.strict_encode64("#{client_id}:#{client_secret}")
    req.body = URI.encode_www_form username: username, password: password, scope: scope, grant_type: 'password'
  end
end
verify_token(token, issuer:, audience:, client_id:) click to toggle source

validate the token

# File lib/okta/jwt.rb, line 47
def verify_token(token, issuer:, audience:, client_id:)
  header, payload = token.split('.').first(2).map{|encoded| JSON.parse(Base64.decode64(encoded))}

  # validate claims
  raise InvalidToken.new('Invalid issuer')    if payload['iss'] != issuer
  raise InvalidToken.new('Invalid audience')  if payload['aud'] != audience
  raise InvalidToken.new('Invalid client')    if !Array(client_id).include?(payload['cid'])
  raise InvalidToken.new('Token is expired')  if payload['exp'].to_i <= Time.now.to_i

  # validate signature
  jwk = JSON::JWK.new(get_jwk(header, payload))
  JSON::JWT.decode(token, jwk.to_key)
end