lib/rodauth/features/oauth_jwt.rb in rodauth-oauth-0.7.4 vs lib/rodauth/features/oauth_jwt.rb in rodauth-oauth-0.8.0

- old
+ new

@@ -13,12 +13,18 @@ auth_value_method :oauth_jwt_subject_type, "public" # public, pairwise auth_value_method :oauth_jwt_subject_secret, nil # salt for pairwise generation auth_value_method :oauth_jwt_token_issuer, nil - auth_value_method :oauth_application_jws_jwk_column, nil + auth_value_method :oauth_applications_jws_jwk_column, :jws_jwk + auth_value_method :oauth_applications_jwt_public_key_column, :jwt_public_key + translatable_method :oauth_applications_jws_jwk_label, "JSON Web Keys" + translatable_method :oauth_applications_jwt_public_key_label, "Public key" + auth_value_method :oauth_application_jws_jwk_param, :jws_jwk + auth_value_method :oauth_application_jwt_public_key_param, :jwt_public_key + auth_value_method :oauth_jwt_key, nil auth_value_method :oauth_jwt_public_key, nil auth_value_method :oauth_jwt_algorithm, "HS256" auth_value_method :oauth_jwt_jwe_key, nil @@ -111,13 +117,11 @@ request_object = param_or_nil("request") return super unless request_object && oauth_application - jws_jwk = if oauth_application[oauth_application_jws_jwk_column] - jwk = oauth_application[oauth_application_jws_jwk_column] - + jws_jwk = if (jwk = oauth_application[oauth_applications_jws_jwk_column]) jwk = JSON.parse(jwk, symbolize_names: true) if jwk && jwk.is_a?(String) else redirect_response_error("invalid_request_object") end @@ -143,55 +147,10 @@ super end # /token - def require_oauth_application - # requset authentication optional for assertions - return super unless param("grant_type") == "urn:ietf:params:oauth:grant-type:jwt-bearer" - - claims = jwt_decode(param("assertion")) - - redirect_response_error("invalid_grant") unless claims - - @oauth_application = db[oauth_applications_table].where(oauth_applications_client_id_column => claims["client_id"]).first - - authorization_required unless @oauth_application - end - - def validate_oauth_token_params - if param("grant_type") == "urn:ietf:params:oauth:grant-type:jwt-bearer" - redirect_response_error("invalid_client") unless param_or_nil("assertion") - else - super - end - end - - def create_oauth_token - if param("grant_type") == "urn:ietf:params:oauth:grant-type:jwt-bearer" - create_oauth_token_from_assertion - else - super - end - end - - def create_oauth_token_from_assertion - claims = jwt_decode(param("assertion")) - - account = account_ds(claims["sub"]).first - - redirect_response_error("invalid_client") unless oauth_application && account - - create_params = { - oauth_tokens_account_id_column => claims["sub"], - oauth_tokens_oauth_application_id_column => oauth_application[oauth_applications_id_column], - oauth_tokens_scopes_column => claims["scope"] - } - - generate_oauth_token(create_params, false) - end - def generate_oauth_token(params = {}, should_generate_refresh_token = true) create_params = { oauth_grants_expires_in_column => Sequel.date_add(Sequel::CURRENT_TIMESTAMP, seconds: oauth_token_expires_in) }.merge(params) @@ -293,11 +252,21 @@ token_endpoint_auth_signing_alg_values_supported: [oauth_jwt_algorithm] metadata end def _jwt_key - @_jwt_key ||= oauth_jwt_key || (oauth_application[oauth_applications_client_secret_column] if oauth_application) + @_jwt_key ||= oauth_jwt_key || begin + if oauth_application + + if (jwk = oauth_application[oauth_applications_jws_jwk_column]) + jwk = JSON.parse(jwk, symbolize_names: true) if jwk && jwk.is_a?(String) + jwk + else + oauth_application[oauth_applications_jwt_public_key_column] + end + end + end end # Resource Server only! # # returns the jwks set from the authorization server. @@ -344,12 +313,12 @@ def verify_jti(jti, claims) generate_jti(claims) == jti end - def verify_aud(aud, claims) - aud == (oauth_jwt_audience || claims["client_id"]) + def verify_aud(expected_aud, aud) + expected_aud == aud end if defined?(JSON::JWT) def jwk_import(data) @@ -377,10 +346,12 @@ def jwt_decode( token, jws_key: oauth_jwt_public_key || _jwt_key, verify_claims: true, verify_jti: true, + verify_iss: true, + verify_aud: false, ** ) token = JSON::JWT.decode(token, oauth_jwt_jwe_key).plain_text if oauth_jwt_jwe_key claims = if is_authorization_server? @@ -391,15 +362,19 @@ end elsif (jwks = auth_server_jwks_set) JSON::JWT.decode(token, JSON::JWK::Set.new(jwks)) end - if verify_claims && !(claims[:iss] == issuer && - verify_aud(claims[:aud], claims) && - (!claims[:iat] || Time.at(claims[:iat]) > (Time.now - oauth_token_expires_in)) && - (!claims[:exp] || Time.at(claims[:exp]) > Time.now) && - (!verify_jti || verify_jti(claims[:jti], claims))) + now = Time.now + if verify_claims && ( + (!claims[:exp] || Time.at(claims[:exp]) < now) && + (claims[:nbf] && Time.at(claims[:nbf]) < now) && + (claims[:iat] && Time.at(claims[:iat]) < now) && + (verify_iss && claims[:iss] != issuer) && + (verify_aud && !verify_aud(claims[:aud], claims[:client_id])) && + (verify_jti && !verify_jti(claims[:jti], claims)) + ) return end claims rescue JSON::JWT::Exception @@ -454,11 +429,13 @@ def jwt_decode( token, jws_key: oauth_jwt_public_key || _jwt_key, jws_algorithm: oauth_jwt_algorithm, verify_claims: true, - verify_jti: true + verify_jti: true, + verify_iss: true, + verify_aud: false ) # decrypt jwe token = JWE.decrypt(token, oauth_jwt_jwe_key) if oauth_jwt_jwe_key # verifying the JWT implies verifying: @@ -470,11 +447,11 @@ # subject can't be verified automatically without having access to the account id, # which we don't because that's the whole point. # verify_claims_params = if verify_claims { - verify_iss: true, + verify_iss: verify_iss, iss: issuer, # can't use stock aud verification, as it's dependent on the client application id verify_aud: false, verify_jti: (verify_jti ? method(:verify_jti) : false), verify_iat: true @@ -494,10 +471,10 @@ elsif (jwks = auth_server_jwks_set) algorithms = jwks[:keys].select { |k| k[:use] == "sig" }.map { |k| k[:alg] } JWT.decode(token, nil, true, jwks: jwks, algorithms: algorithms, **verify_claims_params).first end - return if verify_claims && !verify_aud(claims["aud"], claims) + return if verify_claims && verify_aud && !verify_aud(claims["aud"], claims["client_id"]) claims rescue JWT::DecodeError, JWT::JWKError nil end