lib/rodauth/features/oauth_jwt.rb in rodauth-oauth-0.0.5 vs lib/rodauth/features/oauth_jwt.rb in rodauth-oauth-0.0.6

- old
+ new

@@ -6,10 +6,12 @@ Feature.define(:oauth_jwt) do depends :oauth auth_value_method :oauth_jwt_token_issuer, "Example" + auth_value_method :oauth_application_jws_jwk_column, nil + 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 @@ -18,10 +20,13 @@ auth_value_method :oauth_jwt_jwe_encryption_method, nil auth_value_method :oauth_jwt_jwe_copyright, nil auth_value_method :oauth_jwt_audience, nil + auth_value_method :request_uri_not_supported_message, "request uri is unsupported" + auth_value_method :invalid_request_object_message, "request object is invalid" + auth_value_methods( :jwt_encode, :jwt_decode, :jwks_set ) @@ -58,10 +63,52 @@ jwt_token end end + # /authorize + + def validate_oauth_grant_params + # TODO: add support for requst_uri + redirect_response_error("request_uri_not_supported") if param_or_nil("request_uri") + + 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] + + if jwk + jwk = JSON.parse(jwk, symbolize_names: true) if jwk.is_a?(String) + end + else + redirect_response_error("invalid_request_object") + end + + claims = jwt_decode(request_object, jws_key: jwk_import(jws_jwk), jws_algorithm: jwk[:alg]) + + redirect_response_error("invalid_request_object") unless claims + + # If signed, the Authorization Request + # Object SHOULD contain the Claims "iss" (issuer) and "aud" (audience) + # as members, with their semantics being the same as defined in the JWT + # [RFC7519] specification. The value of "aud" should be the value of + # the Authorization Server (AS) "issuer" as defined in RFC8414 + # [RFC8414]. + claims.delete(:iss) + audience = claims.delete(:aud) + + redirect_response_error("invalid_request_object") if audience && audience != authorization_server_url + + claims.each do |k, v| + request.params[k.to_s] = v + end + + super + end + # /token def before_token # requset authentication optional for assertions return if param("grant_type") == "urn:ietf:params:oauth:grant-type:jwt-bearer" @@ -232,10 +279,14 @@ end if defined?(JSON::JWT) # :nocov: + def jwk_import(data) + JSON::JWK.new(data) + end + # json-jwt def jwt_encode(payload) jwt = JSON::JWT.new(payload) jwk = JSON::JWK.new(_jwt_key) @@ -249,19 +300,15 @@ oauth_jwt_jwe_encryption_method.to_sym) end jwt.to_s end - def jwt_decode(token) - return @jwt_token if defined?(@jwt_token) - + def jwt_decode(token, jws_key: oauth_jwt_public_key || _jwt_key, **) token = JSON::JWT.decode(token, oauth_jwt_jwe_key).plain_text if oauth_jwt_jwe_key - jwk = oauth_jwt_public_key || _jwt_key - - @jwt_token = if jwk - JSON::JWT.decode(token, jwk) + @jwt_token = if jws_key + JSON::JWT.decode(token, jws_key) elsif !is_authorization_server? && auth_server_jwks_set JSON::JWT.decode(token, JSON::JWK::Set.new(auth_server_jwks_set)) end rescue JSON::JWT::Exception nil @@ -277,10 +324,14 @@ # :nocov: elsif defined?(JWT) # ruby-jwt + def jwk_import(data) + JWT::JWK.import(data).keypair + end + def jwt_encode(payload) headers = {} key = _jwt_key @@ -310,21 +361,17 @@ end token end - def jwt_decode(token) - return @jwt_token if defined?(@jwt_token) - + def jwt_decode(token, jws_key: oauth_jwt_public_key || _jwt_key, jws_algorithm: oauth_jwt_algorithm) # decrypt jwe token = JWE.decrypt(token, oauth_jwt_jwe_key) if oauth_jwt_jwe_key # decode jwt - key = oauth_jwt_public_key || _jwt_key - - @jwt_token = if key - JWT.decode(token, key, true, algorithms: [oauth_jwt_algorithm]).first + @jwt_token = if jws_key + JWT.decode(token, jws_key, true, algorithms: [jws_algorithm]).first elsif !is_authorization_server? && auth_server_jwks_set algorithms = auth_server_jwks_set[:keys].select { |k| k[:use] == "sig" }.map { |k| k[:alg] } JWT.decode(token, nil, true, jwks: auth_server_jwks_set, algorithms: algorithms).first end rescue JWT::DecodeError, JWT::JWKError @@ -337,21 +384,33 @@ (JWT::JWK.new(oauth_jwt_jwe_public_key).export.merge(use: "enc", alg: oauth_jwt_jwe_algorithm) if oauth_jwt_jwe_public_key) ].compact end else # :nocov: + def jwk_import(_data) + raise "#{__method__} is undefined, redefine it or require either \"jwt\" or \"json-jwt\"" + end + def jwt_encode(_token) raise "#{__method__} is undefined, redefine it or require either \"jwt\" or \"json-jwt\"" end - def jwt_decode(_token) + def jwt_decode(_token, **) raise "#{__method__} is undefined, redefine it or require either \"jwt\" or \"json-jwt\"" end def jwks_set raise "#{__method__} is undefined, redefine it or require either \"jwt\" or \"json-jwt\"" end # :nocov: + end + + def validate_oauth_revoke_params + token_hint = param_or_nil("token_type_hint") + + throw(:rodauth_error) if !token_hint || token_hint == "access_token" + + super end route(:oauth_jwks) do |r| r.get do json_response_success({ keys: jwks_set })