rodauth-oauth 0.10.4 → 1.0.0.pre.beta2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/MIGRATION-GUIDE-v1.md +286 -0
 - data/README.md +28 -35
 - data/doc/release_notes/1_0_0_beta1.md +38 -0
 - data/doc/release_notes/1_0_0_beta2.md +34 -0
 - data/lib/generators/rodauth/oauth/install_generator.rb +0 -1
 - data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb +21 -11
 - data/lib/generators/rodauth/oauth/templates/app/views/rodauth/device_search.html.erb +1 -1
 - data/lib/generators/rodauth/oauth/templates/app/views/rodauth/device_verification.html.erb +2 -2
 - data/lib/generators/rodauth/oauth/templates/app/views/rodauth/new_oauth_application.html.erb +1 -6
 - data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application.html.erb +0 -2
 - data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application_oauth_grants.html.erb +41 -0
 - data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_applications.html.erb +2 -2
 - data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_grants.html.erb +37 -0
 - data/lib/generators/rodauth/oauth/templates/db/migrate/create_rodauth_oauth.rb +57 -57
 - data/lib/rodauth/features/oauth_application_management.rb +61 -74
 - data/lib/rodauth/features/oauth_assertion_base.rb +19 -23
 - data/lib/rodauth/features/oauth_authorization_code_grant.rb +62 -90
 - data/lib/rodauth/features/oauth_authorize_base.rb +115 -22
 - data/lib/rodauth/features/oauth_base.rb +397 -315
 - data/lib/rodauth/features/oauth_client_credentials_grant.rb +20 -18
 - data/lib/rodauth/features/{oauth_device_grant.rb → oauth_device_code_grant.rb} +62 -73
 - data/lib/rodauth/features/oauth_dynamic_client_registration.rb +52 -31
 - data/lib/rodauth/features/oauth_grant_management.rb +70 -0
 - data/lib/rodauth/features/oauth_implicit_grant.rb +29 -27
 - data/lib/rodauth/features/oauth_jwt.rb +53 -689
 - data/lib/rodauth/features/oauth_jwt_base.rb +458 -0
 - data/lib/rodauth/features/oauth_jwt_bearer_grant.rb +48 -17
 - data/lib/rodauth/features/oauth_jwt_jwks.rb +47 -0
 - data/lib/rodauth/features/oauth_jwt_secured_authorization_request.rb +116 -0
 - data/lib/rodauth/features/oauth_management_base.rb +2 -0
 - data/lib/rodauth/features/oauth_pkce.rb +22 -26
 - data/lib/rodauth/features/oauth_resource_indicators.rb +33 -25
 - data/lib/rodauth/features/oauth_resource_server.rb +59 -0
 - data/lib/rodauth/features/oauth_saml_bearer_grant.rb +7 -1
 - data/lib/rodauth/features/oauth_token_introspection.rb +76 -46
 - data/lib/rodauth/features/oauth_token_revocation.rb +46 -33
 - data/lib/rodauth/features/oidc.rb +382 -241
 - data/lib/rodauth/features/oidc_dynamic_client_registration.rb +127 -51
 - data/lib/rodauth/features/oidc_rp_initiated_logout.rb +115 -0
 - data/lib/rodauth/oauth/database_extensions.rb +8 -6
 - data/lib/rodauth/oauth/http_extensions.rb +74 -0
 - data/lib/rodauth/oauth/railtie.rb +20 -0
 - data/lib/rodauth/oauth/ttl_store.rb +2 -0
 - data/lib/rodauth/oauth/version.rb +1 -1
 - data/lib/rodauth/oauth.rb +29 -1
 - data/locales/en.yml +34 -22
 - data/locales/pt.yml +34 -22
 - data/templates/authorize.str +19 -17
 - data/templates/device_search.str +1 -1
 - data/templates/device_verification.str +2 -2
 - data/templates/jwks_field.str +1 -0
 - data/templates/new_oauth_application.str +1 -2
 - data/templates/oauth_application.str +2 -2
 - data/templates/oauth_application_oauth_grants.str +54 -0
 - data/templates/oauth_applications.str +2 -2
 - data/templates/oauth_grants.str +52 -0
 - metadata +23 -16
 - data/lib/generators/rodauth/oauth/templates/app/models/oauth_token.rb +0 -4
 - data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application_oauth_tokens.html.erb +0 -39
 - data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_tokens.html.erb +0 -35
 - data/lib/rodauth/features/oauth.rb +0 -9
 - data/lib/rodauth/features/oauth_http_mac.rb +0 -86
 - data/lib/rodauth/features/oauth_token_management.rb +0 -81
 - data/lib/rodauth/oauth/refinements.rb +0 -48
 - data/templates/jwt_public_key_field.str +0 -4
 - data/templates/oauth_application_oauth_tokens.str +0 -52
 - data/templates/oauth_tokens.str +0 -50
 
| 
         @@ -0,0 +1,458 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require "rodauth/oauth"
         
     | 
| 
      
 4 
     | 
    
         
            +
            require "rodauth/oauth/http_extensions"
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            module Rodauth
         
     | 
| 
      
 7 
     | 
    
         
            +
              Feature.define(:oauth_jwt_base, :OauthJwtBase) do
         
     | 
| 
      
 8 
     | 
    
         
            +
                depends :oauth_base
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                auth_value_method :oauth_application_jwt_public_key_param, "jwt_public_key"
         
     | 
| 
      
 11 
     | 
    
         
            +
                auth_value_method :oauth_application_jwks_param, "jwks"
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                auth_value_method :oauth_jwt_keys, {}
         
     | 
| 
      
 14 
     | 
    
         
            +
                auth_value_method :oauth_jwt_public_keys, {}
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                auth_value_method :oauth_jwt_jwe_keys, {}
         
     | 
| 
      
 17 
     | 
    
         
            +
                auth_value_method :oauth_jwt_jwe_public_keys, {}
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                auth_value_method :oauth_jwt_jwe_copyright, nil
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                auth_value_methods(
         
     | 
| 
      
 22 
     | 
    
         
            +
                  :jwt_encode,
         
     | 
| 
      
 23 
     | 
    
         
            +
                  :jwt_decode,
         
     | 
| 
      
 24 
     | 
    
         
            +
                  :jwt_decode_no_key,
         
     | 
| 
      
 25 
     | 
    
         
            +
                  :generate_jti,
         
     | 
| 
      
 26 
     | 
    
         
            +
                  :oauth_jwt_issuer,
         
     | 
| 
      
 27 
     | 
    
         
            +
                  :oauth_jwt_audience
         
     | 
| 
      
 28 
     | 
    
         
            +
                )
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                private
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                def oauth_jwt_issuer
         
     | 
| 
      
 33 
     | 
    
         
            +
                  # The JWT MUST contain an "iss" (issuer) claim that contains a
         
     | 
| 
      
 34 
     | 
    
         
            +
                  # unique identifier for the entity that issued the JWT.
         
     | 
| 
      
 35 
     | 
    
         
            +
                  @oauth_jwt_issuer ||= authorization_server_url
         
     | 
| 
      
 36 
     | 
    
         
            +
                end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                def oauth_jwt_audience
         
     | 
| 
      
 39 
     | 
    
         
            +
                  # The JWT MUST contain an "aud" (audience) claim containing a
         
     | 
| 
      
 40 
     | 
    
         
            +
                  # value that identifies the authorization server as an intended
         
     | 
| 
      
 41 
     | 
    
         
            +
                  # audience.  The token endpoint URL of the authorization server
         
     | 
| 
      
 42 
     | 
    
         
            +
                  # MAY be used as a value for an "aud" element to identify the
         
     | 
| 
      
 43 
     | 
    
         
            +
                  # authorization server as an intended audience of the JWT.
         
     | 
| 
      
 44 
     | 
    
         
            +
                  @oauth_jwt_audience ||= if is_authorization_server?
         
     | 
| 
      
 45 
     | 
    
         
            +
                                            oauth_application[oauth_applications_client_id_column]
         
     | 
| 
      
 46 
     | 
    
         
            +
                                          else
         
     | 
| 
      
 47 
     | 
    
         
            +
                                            metadata = authorization_server_metadata
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                                            return unless metadata
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                                            metadata[:token_endpoint]
         
     | 
| 
      
 52 
     | 
    
         
            +
                                          end
         
     | 
| 
      
 53 
     | 
    
         
            +
                end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                def grant_from_application?(grant_or_claims, oauth_application)
         
     | 
| 
      
 56 
     | 
    
         
            +
                  return super if grant_or_claims[oauth_grants_id_column]
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                  if grant_or_claims["client_id"]
         
     | 
| 
      
 59 
     | 
    
         
            +
                    grant_or_claims["client_id"] == oauth_application[oauth_applications_client_id_column]
         
     | 
| 
      
 60 
     | 
    
         
            +
                  else
         
     | 
| 
      
 61 
     | 
    
         
            +
                    Array(grant_or_claims["aud"]).include?(oauth_application[oauth_applications_client_id_column])
         
     | 
| 
      
 62 
     | 
    
         
            +
                  end
         
     | 
| 
      
 63 
     | 
    
         
            +
                end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                def jwt_subject(oauth_grant, client_application = oauth_application)
         
     | 
| 
      
 66 
     | 
    
         
            +
                  account_id = oauth_grant[oauth_grants_account_id_column]
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                  return account_id.to_s if account_id
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                  client_application[oauth_applications_client_id_column]
         
     | 
| 
      
 71 
     | 
    
         
            +
                end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                def oauth_server_metadata_body(path = nil)
         
     | 
| 
      
 74 
     | 
    
         
            +
                  metadata = super
         
     | 
| 
      
 75 
     | 
    
         
            +
                  metadata.merge! \
         
     | 
| 
      
 76 
     | 
    
         
            +
                    token_endpoint_auth_signing_alg_values_supported: oauth_jwt_keys.keys.uniq
         
     | 
| 
      
 77 
     | 
    
         
            +
                  metadata
         
     | 
| 
      
 78 
     | 
    
         
            +
                end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                def _jwt_key
         
     | 
| 
      
 81 
     | 
    
         
            +
                  @_jwt_key ||= (oauth_application_jwks(oauth_application) if oauth_application)
         
     | 
| 
      
 82 
     | 
    
         
            +
                end
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                def _jwt_public_key
         
     | 
| 
      
 85 
     | 
    
         
            +
                  @_jwt_public_key ||= if oauth_application
         
     | 
| 
      
 86 
     | 
    
         
            +
                                         oauth_application_jwks(oauth_application)
         
     | 
| 
      
 87 
     | 
    
         
            +
                                       else
         
     | 
| 
      
 88 
     | 
    
         
            +
                                         _jwt_key
         
     | 
| 
      
 89 
     | 
    
         
            +
                                       end
         
     | 
| 
      
 90 
     | 
    
         
            +
                end
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                # Resource Server only!
         
     | 
| 
      
 93 
     | 
    
         
            +
                #
         
     | 
| 
      
 94 
     | 
    
         
            +
                # returns the jwks set from the authorization server.
         
     | 
| 
      
 95 
     | 
    
         
            +
                def auth_server_jwks_set
         
     | 
| 
      
 96 
     | 
    
         
            +
                  metadata = authorization_server_metadata
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
                  return unless metadata && (jwks_uri = metadata[:jwks_uri])
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
                  jwks_uri = URI(jwks_uri)
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
                  http_request_with_cache(jwks_uri)
         
     | 
| 
      
 103 
     | 
    
         
            +
                end
         
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
      
 105 
     | 
    
         
            +
                def generate_jti(payload)
         
     | 
| 
      
 106 
     | 
    
         
            +
                  # Use the key and iat to create a unique key per request to prevent replay attacks
         
     | 
| 
      
 107 
     | 
    
         
            +
                  jti_raw = [
         
     | 
| 
      
 108 
     | 
    
         
            +
                    payload[:aud] || payload["aud"],
         
     | 
| 
      
 109 
     | 
    
         
            +
                    payload[:iat] || payload["iat"]
         
     | 
| 
      
 110 
     | 
    
         
            +
                  ].join(":").to_s
         
     | 
| 
      
 111 
     | 
    
         
            +
                  Digest::SHA256.hexdigest(jti_raw)
         
     | 
| 
      
 112 
     | 
    
         
            +
                end
         
     | 
| 
      
 113 
     | 
    
         
            +
             
     | 
| 
      
 114 
     | 
    
         
            +
                def verify_jti(jti, claims)
         
     | 
| 
      
 115 
     | 
    
         
            +
                  generate_jti(claims) == jti
         
     | 
| 
      
 116 
     | 
    
         
            +
                end
         
     | 
| 
      
 117 
     | 
    
         
            +
             
     | 
| 
      
 118 
     | 
    
         
            +
                def verify_aud(expected_aud, aud)
         
     | 
| 
      
 119 
     | 
    
         
            +
                  expected_aud == aud
         
     | 
| 
      
 120 
     | 
    
         
            +
                end
         
     | 
| 
      
 121 
     | 
    
         
            +
             
     | 
| 
      
 122 
     | 
    
         
            +
                def oauth_application_jwks(oauth_application)
         
     | 
| 
      
 123 
     | 
    
         
            +
                  jwks = oauth_application[oauth_applications_jwks_column]
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
                  if jwks
         
     | 
| 
      
 126 
     | 
    
         
            +
                    jwks = JSON.parse(jwks, symbolize_names: true) if jwks.is_a?(String)
         
     | 
| 
      
 127 
     | 
    
         
            +
                    return jwks
         
     | 
| 
      
 128 
     | 
    
         
            +
                  end
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
      
 130 
     | 
    
         
            +
                  jwks_uri = oauth_application[oauth_applications_jwks_uri_column]
         
     | 
| 
      
 131 
     | 
    
         
            +
             
     | 
| 
      
 132 
     | 
    
         
            +
                  return unless jwks_uri
         
     | 
| 
      
 133 
     | 
    
         
            +
             
     | 
| 
      
 134 
     | 
    
         
            +
                  jwks_uri = URI(jwks_uri)
         
     | 
| 
      
 135 
     | 
    
         
            +
             
     | 
| 
      
 136 
     | 
    
         
            +
                  http_request_with_cache(jwks_uri)
         
     | 
| 
      
 137 
     | 
    
         
            +
                end
         
     | 
| 
      
 138 
     | 
    
         
            +
             
     | 
| 
      
 139 
     | 
    
         
            +
                if defined?(JSON::JWT)
         
     | 
| 
      
 140 
     | 
    
         
            +
                  # json-jwt
         
     | 
| 
      
 141 
     | 
    
         
            +
             
     | 
| 
      
 142 
     | 
    
         
            +
                  auth_value_method :oauth_jwt_jws_algorithms_supported, %w[
         
     | 
| 
      
 143 
     | 
    
         
            +
                    HS256 HS384 HS512
         
     | 
| 
      
 144 
     | 
    
         
            +
                    RS256 RS384 RS512
         
     | 
| 
      
 145 
     | 
    
         
            +
                    PS256 PS384 PS512
         
     | 
| 
      
 146 
     | 
    
         
            +
                    ES256 ES384 ES512 ES256K
         
     | 
| 
      
 147 
     | 
    
         
            +
                  ]
         
     | 
| 
      
 148 
     | 
    
         
            +
                  auth_value_method :oauth_jwt_jwe_algorithms_supported, %w[
         
     | 
| 
      
 149 
     | 
    
         
            +
                    RSA1_5 RSA-OAEP dir A128KW A256KW
         
     | 
| 
      
 150 
     | 
    
         
            +
                  ]
         
     | 
| 
      
 151 
     | 
    
         
            +
                  auth_value_method :oauth_jwt_jwe_encryption_methods_supported, %w[
         
     | 
| 
      
 152 
     | 
    
         
            +
                    A128GCM A256GCM A128CBC-HS256 A256CBC-HS512
         
     | 
| 
      
 153 
     | 
    
         
            +
                  ]
         
     | 
| 
      
 154 
     | 
    
         
            +
             
     | 
| 
      
 155 
     | 
    
         
            +
                  def jwk_export(key)
         
     | 
| 
      
 156 
     | 
    
         
            +
                    JSON::JWK.new(key)
         
     | 
| 
      
 157 
     | 
    
         
            +
                  end
         
     | 
| 
      
 158 
     | 
    
         
            +
             
     | 
| 
      
 159 
     | 
    
         
            +
                  def jwt_encode(payload,
         
     | 
| 
      
 160 
     | 
    
         
            +
                                 jwks: nil,
         
     | 
| 
      
 161 
     | 
    
         
            +
                                 encryption_algorithm: oauth_jwt_jwe_keys.keys.dig(0, 0),
         
     | 
| 
      
 162 
     | 
    
         
            +
                                 encryption_method: oauth_jwt_jwe_keys.keys.dig(0, 1),
         
     | 
| 
      
 163 
     | 
    
         
            +
                                 jwe_key: oauth_jwt_jwe_keys[[encryption_algorithm,
         
     | 
| 
      
 164 
     | 
    
         
            +
                                                              encryption_method]],
         
     | 
| 
      
 165 
     | 
    
         
            +
                                 signing_algorithm: oauth_jwt_keys.keys.first)
         
     | 
| 
      
 166 
     | 
    
         
            +
                    payload[:jti] = generate_jti(payload)
         
     | 
| 
      
 167 
     | 
    
         
            +
                    jwt = JSON::JWT.new(payload)
         
     | 
| 
      
 168 
     | 
    
         
            +
             
     | 
| 
      
 169 
     | 
    
         
            +
                    key = oauth_jwt_keys[signing_algorithm] || _jwt_key
         
     | 
| 
      
 170 
     | 
    
         
            +
                    key = key.first if key.is_a?(Array)
         
     | 
| 
      
 171 
     | 
    
         
            +
             
     | 
| 
      
 172 
     | 
    
         
            +
                    jwk = JSON::JWK.new(key || "")
         
     | 
| 
      
 173 
     | 
    
         
            +
             
     | 
| 
      
 174 
     | 
    
         
            +
                    jwt = jwt.sign(jwk, signing_algorithm)
         
     | 
| 
      
 175 
     | 
    
         
            +
                    jwt.kid = jwk.thumbprint
         
     | 
| 
      
 176 
     | 
    
         
            +
             
     | 
| 
      
 177 
     | 
    
         
            +
                    if jwks && (jwk = jwks.find { |k| k[:use] == "enc" && k[:alg] == encryption_algorithm && k[:enc] == encryption_method })
         
     | 
| 
      
 178 
     | 
    
         
            +
                      jwk = JSON::JWK.new(jwk)
         
     | 
| 
      
 179 
     | 
    
         
            +
                      jwe = jwt.encrypt(jwk, encryption_algorithm.to_sym, encryption_method.to_sym)
         
     | 
| 
      
 180 
     | 
    
         
            +
                      jwe.to_s
         
     | 
| 
      
 181 
     | 
    
         
            +
                    elsif jwe_key
         
     | 
| 
      
 182 
     | 
    
         
            +
                      jwe_key = jwe_key.first if jwe_key.is_a?(Array)
         
     | 
| 
      
 183 
     | 
    
         
            +
                      algorithm = encryption_algorithm.to_sym if encryption_algorithm
         
     | 
| 
      
 184 
     | 
    
         
            +
                      meth = encryption_method.to_sym if encryption_method
         
     | 
| 
      
 185 
     | 
    
         
            +
                      jwt.encrypt(jwe_key, algorithm, meth)
         
     | 
| 
      
 186 
     | 
    
         
            +
                    else
         
     | 
| 
      
 187 
     | 
    
         
            +
                      jwt.to_s
         
     | 
| 
      
 188 
     | 
    
         
            +
                    end
         
     | 
| 
      
 189 
     | 
    
         
            +
                  end
         
     | 
| 
      
 190 
     | 
    
         
            +
             
     | 
| 
      
 191 
     | 
    
         
            +
                  def jwt_decode(
         
     | 
| 
      
 192 
     | 
    
         
            +
                    token,
         
     | 
| 
      
 193 
     | 
    
         
            +
                    jwks: nil,
         
     | 
| 
      
 194 
     | 
    
         
            +
                    jws_algorithm: oauth_jwt_public_keys.keys.first || oauth_jwt_keys.keys.first,
         
     | 
| 
      
 195 
     | 
    
         
            +
                    jws_key: oauth_jwt_keys[jws_algorithm] || _jwt_key,
         
     | 
| 
      
 196 
     | 
    
         
            +
                    jws_encryption_algorithm: oauth_jwt_jwe_keys.keys.dig(0, 0),
         
     | 
| 
      
 197 
     | 
    
         
            +
                    jws_encryption_method: oauth_jwt_jwe_keys.keys.dig(0, 1),
         
     | 
| 
      
 198 
     | 
    
         
            +
                    jwe_key: oauth_jwt_jwe_keys[[jws_encryption_algorithm, jws_encryption_method]] || oauth_jwt_jwe_keys.values.first,
         
     | 
| 
      
 199 
     | 
    
         
            +
                    verify_claims: true,
         
     | 
| 
      
 200 
     | 
    
         
            +
                    verify_jti: true,
         
     | 
| 
      
 201 
     | 
    
         
            +
                    verify_iss: true,
         
     | 
| 
      
 202 
     | 
    
         
            +
                    verify_aud: true,
         
     | 
| 
      
 203 
     | 
    
         
            +
                    **
         
     | 
| 
      
 204 
     | 
    
         
            +
                  )
         
     | 
| 
      
 205 
     | 
    
         
            +
                    jws_key = jws_key.first if jws_key.is_a?(Array)
         
     | 
| 
      
 206 
     | 
    
         
            +
             
     | 
| 
      
 207 
     | 
    
         
            +
                    if jwe_key
         
     | 
| 
      
 208 
     | 
    
         
            +
                      jwe_key = jwe_key.first if jwe_key.is_a?(Array)
         
     | 
| 
      
 209 
     | 
    
         
            +
                      token = JSON::JWT.decode(token, jwe_key).plain_text
         
     | 
| 
      
 210 
     | 
    
         
            +
                    end
         
     | 
| 
      
 211 
     | 
    
         
            +
             
     | 
| 
      
 212 
     | 
    
         
            +
                    claims = if is_authorization_server?
         
     | 
| 
      
 213 
     | 
    
         
            +
                               if jwks
         
     | 
| 
      
 214 
     | 
    
         
            +
                                 jwks = jwks[:keys] if jwks.is_a?(Hash)
         
     | 
| 
      
 215 
     | 
    
         
            +
             
     | 
| 
      
 216 
     | 
    
         
            +
                                 enc_algs = [jws_encryption_algorithm].compact
         
     | 
| 
      
 217 
     | 
    
         
            +
                                 enc_meths = [jws_encryption_method].compact
         
     | 
| 
      
 218 
     | 
    
         
            +
             
     | 
| 
      
 219 
     | 
    
         
            +
                                 sig_algs = jws_algorithm ? [jws_algorithm] : jwks.select { |k| k[:use] == "sig" }.map { |k| k[:alg] }
         
     | 
| 
      
 220 
     | 
    
         
            +
                                 sig_algs = sig_algs.compact.map(&:to_sym)
         
     | 
| 
      
 221 
     | 
    
         
            +
             
     | 
| 
      
 222 
     | 
    
         
            +
                                 # JWKs may be set up without a KID, when there's a single one
         
     | 
| 
      
 223 
     | 
    
         
            +
                                 if jwks.size == 1 && !jwks[0][:kid]
         
     | 
| 
      
 224 
     | 
    
         
            +
                                   key = jwks[0]
         
     | 
| 
      
 225 
     | 
    
         
            +
                                   jwk_key = JSON::JWK.new(key)
         
     | 
| 
      
 226 
     | 
    
         
            +
                                   jws = JSON::JWT.decode(token, jwk_key)
         
     | 
| 
      
 227 
     | 
    
         
            +
                                 else
         
     | 
| 
      
 228 
     | 
    
         
            +
                                   jws = JSON::JWT.decode(token, JSON::JWK::Set.new({ keys: jwks }), enc_algs + sig_algs, enc_meths)
         
     | 
| 
      
 229 
     | 
    
         
            +
                                   jws = JSON::JWT.decode(jws.plain_text, JSON::JWK::Set.new({ keys: jwks }), sig_algs) if jws.is_a?(JSON::JWE)
         
     | 
| 
      
 230 
     | 
    
         
            +
                                 end
         
     | 
| 
      
 231 
     | 
    
         
            +
                                 jws
         
     | 
| 
      
 232 
     | 
    
         
            +
                               elsif jws_key
         
     | 
| 
      
 233 
     | 
    
         
            +
                                 JSON::JWT.decode(token, jws_key)
         
     | 
| 
      
 234 
     | 
    
         
            +
                               end
         
     | 
| 
      
 235 
     | 
    
         
            +
                             elsif (jwks = auth_server_jwks_set)
         
     | 
| 
      
 236 
     | 
    
         
            +
                               JSON::JWT.decode(token, JSON::JWK::Set.new(jwks))
         
     | 
| 
      
 237 
     | 
    
         
            +
                             end
         
     | 
| 
      
 238 
     | 
    
         
            +
             
     | 
| 
      
 239 
     | 
    
         
            +
                    now = Time.now
         
     | 
| 
      
 240 
     | 
    
         
            +
                    if verify_claims && (
         
     | 
| 
      
 241 
     | 
    
         
            +
                        (!claims[:exp] || Time.at(claims[:exp]) < now) &&
         
     | 
| 
      
 242 
     | 
    
         
            +
                        (claims[:nbf] && Time.at(claims[:nbf]) < now) &&
         
     | 
| 
      
 243 
     | 
    
         
            +
                        (claims[:iat] && Time.at(claims[:iat]) < now) &&
         
     | 
| 
      
 244 
     | 
    
         
            +
                        (verify_iss && claims[:iss] != oauth_jwt_issuer) &&
         
     | 
| 
      
 245 
     | 
    
         
            +
                        (verify_aud && !verify_aud(claims[:aud], claims[:client_id])) &&
         
     | 
| 
      
 246 
     | 
    
         
            +
                        (verify_jti && !verify_jti(claims[:jti], claims))
         
     | 
| 
      
 247 
     | 
    
         
            +
                      )
         
     | 
| 
      
 248 
     | 
    
         
            +
                      return
         
     | 
| 
      
 249 
     | 
    
         
            +
                    end
         
     | 
| 
      
 250 
     | 
    
         
            +
             
     | 
| 
      
 251 
     | 
    
         
            +
                    claims
         
     | 
| 
      
 252 
     | 
    
         
            +
                  rescue JSON::JWT::Exception
         
     | 
| 
      
 253 
     | 
    
         
            +
                    nil
         
     | 
| 
      
 254 
     | 
    
         
            +
                  end
         
     | 
| 
      
 255 
     | 
    
         
            +
             
     | 
| 
      
 256 
     | 
    
         
            +
                  def jwt_decode_no_key(token)
         
     | 
| 
      
 257 
     | 
    
         
            +
                    jws = JSON::JWT.decode(token, :skip_verification)
         
     | 
| 
      
 258 
     | 
    
         
            +
                    [jws.to_h, jws.header]
         
     | 
| 
      
 259 
     | 
    
         
            +
                  end
         
     | 
| 
      
 260 
     | 
    
         
            +
                elsif defined?(JWT)
         
     | 
| 
      
 261 
     | 
    
         
            +
                  # ruby-jwt
         
     | 
| 
      
 262 
     | 
    
         
            +
                  require "rodauth/oauth/jwe_extensions" if defined?(JWE)
         
     | 
| 
      
 263 
     | 
    
         
            +
             
     | 
| 
      
 264 
     | 
    
         
            +
                  auth_value_method :oauth_jwt_jws_algorithms_supported, %w[
         
     | 
| 
      
 265 
     | 
    
         
            +
                    HS256 HS384 HS512 HS512256
         
     | 
| 
      
 266 
     | 
    
         
            +
                    RS256 RS384 RS512
         
     | 
| 
      
 267 
     | 
    
         
            +
                    ED25519
         
     | 
| 
      
 268 
     | 
    
         
            +
                    ES256 ES384 ES512
         
     | 
| 
      
 269 
     | 
    
         
            +
                    PS256 PS384 PS512
         
     | 
| 
      
 270 
     | 
    
         
            +
                  ]
         
     | 
| 
      
 271 
     | 
    
         
            +
             
     | 
| 
      
 272 
     | 
    
         
            +
                  if defined?(JWE)
         
     | 
| 
      
 273 
     | 
    
         
            +
                    auth_value_methods(
         
     | 
| 
      
 274 
     | 
    
         
            +
                      :oauth_jwt_jwe_algorithms_supported,
         
     | 
| 
      
 275 
     | 
    
         
            +
                      :oauth_jwt_jwe_encryption_methods_supported
         
     | 
| 
      
 276 
     | 
    
         
            +
                    )
         
     | 
| 
      
 277 
     | 
    
         
            +
             
     | 
| 
      
 278 
     | 
    
         
            +
                    def oauth_jwt_jwe_algorithms_supported
         
     | 
| 
      
 279 
     | 
    
         
            +
                      JWE::VALID_ALG
         
     | 
| 
      
 280 
     | 
    
         
            +
                    end
         
     | 
| 
      
 281 
     | 
    
         
            +
             
     | 
| 
      
 282 
     | 
    
         
            +
                    def oauth_jwt_jwe_encryption_methods_supported
         
     | 
| 
      
 283 
     | 
    
         
            +
                      JWE::VALID_ENC
         
     | 
| 
      
 284 
     | 
    
         
            +
                    end
         
     | 
| 
      
 285 
     | 
    
         
            +
                  else
         
     | 
| 
      
 286 
     | 
    
         
            +
                    auth_value_method :oauth_jwt_jwe_algorithms_supported, []
         
     | 
| 
      
 287 
     | 
    
         
            +
                    auth_value_method :oauth_jwt_jwe_encryption_methods_supported, []
         
     | 
| 
      
 288 
     | 
    
         
            +
                  end
         
     | 
| 
      
 289 
     | 
    
         
            +
             
     | 
| 
      
 290 
     | 
    
         
            +
                  def jwk_export(key)
         
     | 
| 
      
 291 
     | 
    
         
            +
                    JWT::JWK.new(key).export
         
     | 
| 
      
 292 
     | 
    
         
            +
                  end
         
     | 
| 
      
 293 
     | 
    
         
            +
             
     | 
| 
      
 294 
     | 
    
         
            +
                  def jwt_encode(payload,
         
     | 
| 
      
 295 
     | 
    
         
            +
                                 signing_algorithm: oauth_jwt_keys.keys.first, **)
         
     | 
| 
      
 296 
     | 
    
         
            +
                    headers = {}
         
     | 
| 
      
 297 
     | 
    
         
            +
             
     | 
| 
      
 298 
     | 
    
         
            +
                    key = oauth_jwt_keys[signing_algorithm] || _jwt_key
         
     | 
| 
      
 299 
     | 
    
         
            +
                    key = key.first if key.is_a?(Array)
         
     | 
| 
      
 300 
     | 
    
         
            +
             
     | 
| 
      
 301 
     | 
    
         
            +
                    case key
         
     | 
| 
      
 302 
     | 
    
         
            +
                    when OpenSSL::PKey::PKey
         
     | 
| 
      
 303 
     | 
    
         
            +
                      jwk = JWT::JWK.new(key)
         
     | 
| 
      
 304 
     | 
    
         
            +
                      headers[:kid] = jwk.kid
         
     | 
| 
      
 305 
     | 
    
         
            +
             
     | 
| 
      
 306 
     | 
    
         
            +
                      key = jwk.keypair
         
     | 
| 
      
 307 
     | 
    
         
            +
                    end
         
     | 
| 
      
 308 
     | 
    
         
            +
             
     | 
| 
      
 309 
     | 
    
         
            +
                    # @see JWT reserved claims - https://tools.ietf.org/html/draft-jones-json-web-token-07#page-7
         
     | 
| 
      
 310 
     | 
    
         
            +
                    payload[:jti] = generate_jti(payload)
         
     | 
| 
      
 311 
     | 
    
         
            +
                    JWT.encode(payload, key, signing_algorithm, headers)
         
     | 
| 
      
 312 
     | 
    
         
            +
                  end
         
     | 
| 
      
 313 
     | 
    
         
            +
             
     | 
| 
      
 314 
     | 
    
         
            +
                  if defined?(JWE)
         
     | 
| 
      
 315 
     | 
    
         
            +
                    def jwt_encode_with_jwe(
         
     | 
| 
      
 316 
     | 
    
         
            +
                      payload,
         
     | 
| 
      
 317 
     | 
    
         
            +
                      jwks: nil,
         
     | 
| 
      
 318 
     | 
    
         
            +
                      encryption_algorithm: oauth_jwt_jwe_keys.keys.dig(0, 0),
         
     | 
| 
      
 319 
     | 
    
         
            +
                      encryption_method: oauth_jwt_jwe_keys.keys.dig(0, 1),
         
     | 
| 
      
 320 
     | 
    
         
            +
                      jwe_key: oauth_jwt_jwe_keys[[encryption_algorithm, encryption_method]],
         
     | 
| 
      
 321 
     | 
    
         
            +
                      **args
         
     | 
| 
      
 322 
     | 
    
         
            +
                    )
         
     | 
| 
      
 323 
     | 
    
         
            +
                      token = jwt_encode_without_jwe(payload, **args)
         
     | 
| 
      
 324 
     | 
    
         
            +
             
     | 
| 
      
 325 
     | 
    
         
            +
                      return token unless encryption_algorithm && encryption_method
         
     | 
| 
      
 326 
     | 
    
         
            +
             
     | 
| 
      
 327 
     | 
    
         
            +
                      if jwks && jwks.any? { |k| k[:use] == "enc" }
         
     | 
| 
      
 328 
     | 
    
         
            +
                        JWE.__rodauth_oauth_encrypt_from_jwks(token, jwks, alg: encryption_algorithm, enc: encryption_method)
         
     | 
| 
      
 329 
     | 
    
         
            +
                      elsif jwe_key
         
     | 
| 
      
 330 
     | 
    
         
            +
                        jwe_key = jwe_key.first if jwe_key.is_a?(Array)
         
     | 
| 
      
 331 
     | 
    
         
            +
                        params = {
         
     | 
| 
      
 332 
     | 
    
         
            +
                          zip: "DEF",
         
     | 
| 
      
 333 
     | 
    
         
            +
                          copyright: oauth_jwt_jwe_copyright
         
     | 
| 
      
 334 
     | 
    
         
            +
                        }
         
     | 
| 
      
 335 
     | 
    
         
            +
                        params[:enc] = encryption_method if encryption_method
         
     | 
| 
      
 336 
     | 
    
         
            +
                        params[:alg] = encryption_algorithm if encryption_algorithm
         
     | 
| 
      
 337 
     | 
    
         
            +
                        JWE.encrypt(token, jwe_key, **params)
         
     | 
| 
      
 338 
     | 
    
         
            +
                      else
         
     | 
| 
      
 339 
     | 
    
         
            +
                        token
         
     | 
| 
      
 340 
     | 
    
         
            +
                      end
         
     | 
| 
      
 341 
     | 
    
         
            +
                    end
         
     | 
| 
      
 342 
     | 
    
         
            +
             
     | 
| 
      
 343 
     | 
    
         
            +
                    alias_method :jwt_encode_without_jwe, :jwt_encode
         
     | 
| 
      
 344 
     | 
    
         
            +
                    alias_method :jwt_encode, :jwt_encode_with_jwe
         
     | 
| 
      
 345 
     | 
    
         
            +
                  end
         
     | 
| 
      
 346 
     | 
    
         
            +
             
     | 
| 
      
 347 
     | 
    
         
            +
                  def jwt_decode(
         
     | 
| 
      
 348 
     | 
    
         
            +
                    token,
         
     | 
| 
      
 349 
     | 
    
         
            +
                    jwks: nil,
         
     | 
| 
      
 350 
     | 
    
         
            +
                    jws_algorithm: oauth_jwt_public_keys.keys.first || oauth_jwt_keys.keys.first,
         
     | 
| 
      
 351 
     | 
    
         
            +
                    jws_key: oauth_jwt_keys[jws_algorithm] || _jwt_key,
         
     | 
| 
      
 352 
     | 
    
         
            +
                    verify_claims: true,
         
     | 
| 
      
 353 
     | 
    
         
            +
                    verify_jti: true,
         
     | 
| 
      
 354 
     | 
    
         
            +
                    verify_iss: true,
         
     | 
| 
      
 355 
     | 
    
         
            +
                    verify_aud: true
         
     | 
| 
      
 356 
     | 
    
         
            +
                  )
         
     | 
| 
      
 357 
     | 
    
         
            +
                    jws_key = jws_key.first if jws_key.is_a?(Array)
         
     | 
| 
      
 358 
     | 
    
         
            +
             
     | 
| 
      
 359 
     | 
    
         
            +
                    # verifying the JWT implies verifying:
         
     | 
| 
      
 360 
     | 
    
         
            +
                    #
         
     | 
| 
      
 361 
     | 
    
         
            +
                    # issuer: check that server generated the token
         
     | 
| 
      
 362 
     | 
    
         
            +
                    # aud: check the audience field (client is who he says he is)
         
     | 
| 
      
 363 
     | 
    
         
            +
                    # iat: check that the token didn't expire
         
     | 
| 
      
 364 
     | 
    
         
            +
                    #
         
     | 
| 
      
 365 
     | 
    
         
            +
                    # subject can't be verified automatically without having access to the account id,
         
     | 
| 
      
 366 
     | 
    
         
            +
                    # which we don't because that's the whole point.
         
     | 
| 
      
 367 
     | 
    
         
            +
                    #
         
     | 
| 
      
 368 
     | 
    
         
            +
                    verify_claims_params = if verify_claims
         
     | 
| 
      
 369 
     | 
    
         
            +
                                             {
         
     | 
| 
      
 370 
     | 
    
         
            +
                                               verify_iss: verify_iss,
         
     | 
| 
      
 371 
     | 
    
         
            +
                                               iss: oauth_jwt_issuer,
         
     | 
| 
      
 372 
     | 
    
         
            +
                                               # can't use stock aud verification, as it's dependent on the client application id
         
     | 
| 
      
 373 
     | 
    
         
            +
                                               verify_aud: false,
         
     | 
| 
      
 374 
     | 
    
         
            +
                                               verify_jti: (verify_jti ? method(:verify_jti) : false),
         
     | 
| 
      
 375 
     | 
    
         
            +
                                               verify_iat: true
         
     | 
| 
      
 376 
     | 
    
         
            +
                                             }
         
     | 
| 
      
 377 
     | 
    
         
            +
                                           else
         
     | 
| 
      
 378 
     | 
    
         
            +
                                             {}
         
     | 
| 
      
 379 
     | 
    
         
            +
                                           end
         
     | 
| 
      
 380 
     | 
    
         
            +
             
     | 
| 
      
 381 
     | 
    
         
            +
                    # decode jwt
         
     | 
| 
      
 382 
     | 
    
         
            +
                    claims = if is_authorization_server?
         
     | 
| 
      
 383 
     | 
    
         
            +
                               if jwks
         
     | 
| 
      
 384 
     | 
    
         
            +
                                 jwks = jwks[:keys] if jwks.is_a?(Hash)
         
     | 
| 
      
 385 
     | 
    
         
            +
             
     | 
| 
      
 386 
     | 
    
         
            +
                                 # JWKs may be set up without a KID, when there's a single one
         
     | 
| 
      
 387 
     | 
    
         
            +
                                 if jwks.size == 1 && !jwks[0][:kid]
         
     | 
| 
      
 388 
     | 
    
         
            +
                                   key = jwks[0]
         
     | 
| 
      
 389 
     | 
    
         
            +
                                   algo = key[:alg]
         
     | 
| 
      
 390 
     | 
    
         
            +
                                   key = JWT::JWK.import(key).keypair
         
     | 
| 
      
 391 
     | 
    
         
            +
                                   JWT.decode(token, key, true, algorithms: [algo], **verify_claims_params).first
         
     | 
| 
      
 392 
     | 
    
         
            +
                                 else
         
     | 
| 
      
 393 
     | 
    
         
            +
                                   algorithms = jws_algorithm ? [jws_algorithm] : jwks.select { |k| k[:use] == "sig" }.map { |k| k[:alg] }
         
     | 
| 
      
 394 
     | 
    
         
            +
                                   JWT.decode(token, nil, true, algorithms: algorithms, jwks: { keys: jwks }, **verify_claims_params).first
         
     | 
| 
      
 395 
     | 
    
         
            +
                                 end
         
     | 
| 
      
 396 
     | 
    
         
            +
                               elsif jws_key
         
     | 
| 
      
 397 
     | 
    
         
            +
                                 JWT.decode(token, jws_key, true, algorithms: [jws_algorithm], **verify_claims_params).first
         
     | 
| 
      
 398 
     | 
    
         
            +
                               end
         
     | 
| 
      
 399 
     | 
    
         
            +
                             elsif (jwks = auth_server_jwks_set)
         
     | 
| 
      
 400 
     | 
    
         
            +
                               algorithms = jwks[:keys].select { |k| k[:use] == "sig" }.map { |k| k[:alg] }
         
     | 
| 
      
 401 
     | 
    
         
            +
                               JWT.decode(token, nil, true, jwks: jwks, algorithms: algorithms, **verify_claims_params).first
         
     | 
| 
      
 402 
     | 
    
         
            +
                             end
         
     | 
| 
      
 403 
     | 
    
         
            +
             
     | 
| 
      
 404 
     | 
    
         
            +
                    return if verify_claims && verify_aud && !verify_aud(claims["aud"], claims["client_id"])
         
     | 
| 
      
 405 
     | 
    
         
            +
             
     | 
| 
      
 406 
     | 
    
         
            +
                    claims
         
     | 
| 
      
 407 
     | 
    
         
            +
                  rescue JWT::DecodeError, JWT::JWKError
         
     | 
| 
      
 408 
     | 
    
         
            +
                    nil
         
     | 
| 
      
 409 
     | 
    
         
            +
                  end
         
     | 
| 
      
 410 
     | 
    
         
            +
             
     | 
| 
      
 411 
     | 
    
         
            +
                  if defined?(JWE)
         
     | 
| 
      
 412 
     | 
    
         
            +
                    def jwt_decode_with_jwe(
         
     | 
| 
      
 413 
     | 
    
         
            +
                      token,
         
     | 
| 
      
 414 
     | 
    
         
            +
                      jwks: nil,
         
     | 
| 
      
 415 
     | 
    
         
            +
                      jws_encryption_algorithm: oauth_jwt_jwe_keys.keys.dig(0, 0),
         
     | 
| 
      
 416 
     | 
    
         
            +
                      jws_encryption_method: oauth_jwt_jwe_keys.keys.dig(0, 1),
         
     | 
| 
      
 417 
     | 
    
         
            +
                      jwe_key: oauth_jwt_jwe_keys[[jws_encryption_algorithm, jws_encryption_method]] || oauth_jwt_jwe_keys.values.first,
         
     | 
| 
      
 418 
     | 
    
         
            +
                      **args
         
     | 
| 
      
 419 
     | 
    
         
            +
                    )
         
     | 
| 
      
 420 
     | 
    
         
            +
             
     | 
| 
      
 421 
     | 
    
         
            +
                      token = if jwks && jwks.any? { |k| k[:use] == "enc" }
         
     | 
| 
      
 422 
     | 
    
         
            +
                                JWE.__rodauth_oauth_decrypt_from_jwks(token, jwks, alg: jws_encryption_algorithm, enc: jws_encryption_method)
         
     | 
| 
      
 423 
     | 
    
         
            +
                              elsif jwe_key
         
     | 
| 
      
 424 
     | 
    
         
            +
                                jwe_key = jwe_key.first if jwe_key.is_a?(Array)
         
     | 
| 
      
 425 
     | 
    
         
            +
                                JWE.decrypt(token, jwe_key)
         
     | 
| 
      
 426 
     | 
    
         
            +
                              else
         
     | 
| 
      
 427 
     | 
    
         
            +
                                token
         
     | 
| 
      
 428 
     | 
    
         
            +
                              end
         
     | 
| 
      
 429 
     | 
    
         
            +
             
     | 
| 
      
 430 
     | 
    
         
            +
                      jwt_decode_without_jwe(token, jwks: jwks, **args)
         
     | 
| 
      
 431 
     | 
    
         
            +
                    rescue JWE::DecodeError => e
         
     | 
| 
      
 432 
     | 
    
         
            +
                      jwt_decode_without_jwe(token, jwks: jwks, **args) if e.message.include?("Not enough or too many segments")
         
     | 
| 
      
 433 
     | 
    
         
            +
                    end
         
     | 
| 
      
 434 
     | 
    
         
            +
             
     | 
| 
      
 435 
     | 
    
         
            +
                    alias_method :jwt_decode_without_jwe, :jwt_decode
         
     | 
| 
      
 436 
     | 
    
         
            +
                    alias_method :jwt_decode, :jwt_decode_with_jwe
         
     | 
| 
      
 437 
     | 
    
         
            +
                  end
         
     | 
| 
      
 438 
     | 
    
         
            +
             
     | 
| 
      
 439 
     | 
    
         
            +
                  def jwt_decode_no_key(token)
         
     | 
| 
      
 440 
     | 
    
         
            +
                    JWT.decode(token, nil, false)
         
     | 
| 
      
 441 
     | 
    
         
            +
                  end
         
     | 
| 
      
 442 
     | 
    
         
            +
                else
         
     | 
| 
      
 443 
     | 
    
         
            +
                  # :nocov:
         
     | 
| 
      
 444 
     | 
    
         
            +
                  def jwk_export(_key)
         
     | 
| 
      
 445 
     | 
    
         
            +
                    raise "#{__method__} is undefined, redefine it or require either \"jwt\" or \"json-jwt\""
         
     | 
| 
      
 446 
     | 
    
         
            +
                  end
         
     | 
| 
      
 447 
     | 
    
         
            +
             
     | 
| 
      
 448 
     | 
    
         
            +
                  def jwt_encode(_token)
         
     | 
| 
      
 449 
     | 
    
         
            +
                    raise "#{__method__} is undefined, redefine it or require either \"jwt\" or \"json-jwt\""
         
     | 
| 
      
 450 
     | 
    
         
            +
                  end
         
     | 
| 
      
 451 
     | 
    
         
            +
             
     | 
| 
      
 452 
     | 
    
         
            +
                  def jwt_decode(_token, **)
         
     | 
| 
      
 453 
     | 
    
         
            +
                    raise "#{__method__} is undefined, redefine it or require either \"jwt\" or \"json-jwt\""
         
     | 
| 
      
 454 
     | 
    
         
            +
                  end
         
     | 
| 
      
 455 
     | 
    
         
            +
                  # :nocov:
         
     | 
| 
      
 456 
     | 
    
         
            +
                end
         
     | 
| 
      
 457 
     | 
    
         
            +
              end
         
     | 
| 
      
 458 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -1,18 +1,31 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
     | 
    
         
            -
            require "rodauth/oauth 
     | 
| 
       4 
     | 
    
         
            -
            require "rodauth/oauth/ttl_store"
         
     | 
| 
      
 3 
     | 
    
         
            +
            require "rodauth/oauth"
         
     | 
| 
       5 
4 
     | 
    
         | 
| 
       6 
5 
     | 
    
         
             
            module Rodauth
         
     | 
| 
       7 
6 
     | 
    
         
             
              Feature.define(:oauth_jwt_bearer_grant, :OauthJwtBearerGrant) do
         
     | 
| 
       8 
7 
     | 
    
         
             
                depends :oauth_assertion_base, :oauth_jwt
         
     | 
| 
       9 
8 
     | 
    
         | 
| 
      
 9 
     | 
    
         
            +
                auth_value_method :max_param_bytesize, nil if Rodauth::VERSION >= "2.26.0"
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
       10 
11 
     | 
    
         
             
                auth_value_methods(
         
     | 
| 
       11 
12 
     | 
    
         
             
                  :require_oauth_application_from_jwt_bearer_assertion_issuer,
         
     | 
| 
       12 
13 
     | 
    
         
             
                  :require_oauth_application_from_jwt_bearer_assertion_subject,
         
     | 
| 
       13 
14 
     | 
    
         
             
                  :account_from_jwt_bearer_assertion
         
     | 
| 
       14 
15 
     | 
    
         
             
                )
         
     | 
| 
       15 
16 
     | 
    
         | 
| 
      
 17 
     | 
    
         
            +
                def oauth_token_endpoint_auth_methods_supported
         
     | 
| 
      
 18 
     | 
    
         
            +
                  if oauth_applications_client_secret_hash_column.nil?
         
     | 
| 
      
 19 
     | 
    
         
            +
                    super | %w[client_secret_jwt private_key_jwt urn:ietf:params:oauth:client-assertion-type:jwt-bearer]
         
     | 
| 
      
 20 
     | 
    
         
            +
                  else
         
     | 
| 
      
 21 
     | 
    
         
            +
                    super | %w[private_key_jwt]
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                def oauth_grant_types_supported
         
     | 
| 
      
 26 
     | 
    
         
            +
                  super | %w[urn:ietf:params:oauth:grant-type:jwt-bearer]
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
       16 
29 
     | 
    
         
             
                private
         
     | 
| 
       17 
30 
     | 
    
         | 
| 
       18 
31 
     | 
    
         
             
                def require_oauth_application_from_jwt_bearer_assertion_issuer(assertion)
         
     | 
| 
         @@ -26,13 +39,37 @@ module Rodauth 
     | 
|
| 
       26 
39 
     | 
    
         
             
                end
         
     | 
| 
       27 
40 
     | 
    
         | 
| 
       28 
41 
     | 
    
         
             
                def require_oauth_application_from_jwt_bearer_assertion_subject(assertion)
         
     | 
| 
       29 
     | 
    
         
            -
                  claims =  
     | 
| 
      
 42 
     | 
    
         
            +
                  claims, header = jwt_decode_no_key(assertion)
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                  client_id = claims["sub"]
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                  case header["alg"]
         
     | 
| 
      
 47 
     | 
    
         
            +
                  when "none"
         
     | 
| 
      
 48 
     | 
    
         
            +
                    # do not accept jwts with no alg set
         
     | 
| 
      
 49 
     | 
    
         
            +
                    authorization_required
         
     | 
| 
      
 50 
     | 
    
         
            +
                  when /\AHS/
         
     | 
| 
      
 51 
     | 
    
         
            +
                    require_oauth_application_from_client_secret_jwt(client_id, assertion, header["alg"])
         
     | 
| 
      
 52 
     | 
    
         
            +
                  else
         
     | 
| 
      
 53 
     | 
    
         
            +
                    require_oauth_application_from_private_key_jwt(client_id, assertion)
         
     | 
| 
      
 54 
     | 
    
         
            +
                  end
         
     | 
| 
      
 55 
     | 
    
         
            +
                end
         
     | 
| 
       30 
56 
     | 
    
         | 
| 
       31 
     | 
    
         
            -
             
     | 
| 
      
 57 
     | 
    
         
            +
                def require_oauth_application_from_client_secret_jwt(client_id, assertion, alg)
         
     | 
| 
      
 58 
     | 
    
         
            +
                  oauth_application = db[oauth_applications_table].where(oauth_applications_client_id_column => client_id).first
         
     | 
| 
      
 59 
     | 
    
         
            +
                  authorization_required unless oauth_application && supports_auth_method?(oauth_application, "client_secret_jwt")
         
     | 
| 
      
 60 
     | 
    
         
            +
                  client_secret = oauth_application[oauth_applications_client_secret_column]
         
     | 
| 
      
 61 
     | 
    
         
            +
                  claims = jwt_assertion(assertion, jws_key: client_secret, jws_algorithm: alg)
         
     | 
| 
      
 62 
     | 
    
         
            +
                  authorization_required unless claims && claims["iss"] == client_id
         
     | 
| 
      
 63 
     | 
    
         
            +
                  oauth_application
         
     | 
| 
      
 64 
     | 
    
         
            +
                end
         
     | 
| 
       32 
65 
     | 
    
         | 
| 
       33 
     | 
    
         
            -
             
     | 
| 
       34 
     | 
    
         
            -
             
     | 
| 
       35 
     | 
    
         
            -
                  ) 
     | 
| 
      
 66 
     | 
    
         
            +
                def require_oauth_application_from_private_key_jwt(client_id, assertion)
         
     | 
| 
      
 67 
     | 
    
         
            +
                  oauth_application = db[oauth_applications_table].where(oauth_applications_client_id_column => client_id).first
         
     | 
| 
      
 68 
     | 
    
         
            +
                  authorization_required unless oauth_application && supports_auth_method?(oauth_application, "private_key_jwt")
         
     | 
| 
      
 69 
     | 
    
         
            +
                  jwks = oauth_application_jwks(oauth_application)
         
     | 
| 
      
 70 
     | 
    
         
            +
                  claims = jwt_assertion(assertion, jwks: jwks)
         
     | 
| 
      
 71 
     | 
    
         
            +
                  authorization_required unless claims
         
     | 
| 
      
 72 
     | 
    
         
            +
                  oauth_application
         
     | 
| 
       36 
73 
     | 
    
         
             
                end
         
     | 
| 
       37 
74 
     | 
    
         | 
| 
       38 
75 
     | 
    
         
             
                def account_from_jwt_bearer_assertion(assertion)
         
     | 
| 
         @@ -43,18 +80,12 @@ module Rodauth 
     | 
|
| 
       43 
80 
     | 
    
         
             
                  account_from_bearer_assertion_subject(claims["sub"])
         
     | 
| 
       44 
81 
     | 
    
         
             
                end
         
     | 
| 
       45 
82 
     | 
    
         | 
| 
       46 
     | 
    
         
            -
                def jwt_assertion(assertion)
         
     | 
| 
       47 
     | 
    
         
            -
                  claims = jwt_decode(assertion, verify_iss: false, verify_aud: false)
         
     | 
| 
       48 
     | 
    
         
            -
                  return unless verify_aud(token_url, claims["aud"])
         
     | 
| 
      
 83 
     | 
    
         
            +
                def jwt_assertion(assertion, **kwargs)
         
     | 
| 
      
 84 
     | 
    
         
            +
                  claims = jwt_decode(assertion, verify_iss: false, verify_aud: false, verify_jti: false, **kwargs)
         
     | 
| 
       49 
85 
     | 
    
         | 
| 
       50 
     | 
    
         
            -
                  claims
         
     | 
| 
       51 
     | 
    
         
            -
                end
         
     | 
| 
      
 86 
     | 
    
         
            +
                  return unless claims && verify_aud(request.url, claims["aud"])
         
     | 
| 
       52 
87 
     | 
    
         | 
| 
       53 
     | 
    
         
            -
             
     | 
| 
       54 
     | 
    
         
            -
                  super.tap do |data|
         
     | 
| 
       55 
     | 
    
         
            -
                    data[:grant_types_supported] << "urn:ietf:params:oauth:grant-type:jwt-bearer"
         
     | 
| 
       56 
     | 
    
         
            -
                    data[:token_endpoint_auth_methods_supported] << "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
         
     | 
| 
       57 
     | 
    
         
            -
                  end
         
     | 
| 
      
 88 
     | 
    
         
            +
                  claims
         
     | 
| 
       58 
89 
     | 
    
         
             
                end
         
     | 
| 
       59 
90 
     | 
    
         
             
              end
         
     | 
| 
       60 
91 
     | 
    
         
             
            end
         
     | 
| 
         @@ -0,0 +1,47 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require "rodauth/oauth"
         
     | 
| 
      
 4 
     | 
    
         
            +
            require "rodauth/oauth/http_extensions"
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            module Rodauth
         
     | 
| 
      
 7 
     | 
    
         
            +
              Feature.define(:oauth_jwt_jwks, :OauthJwtJwks) do
         
     | 
| 
      
 8 
     | 
    
         
            +
                depends :oauth_jwt_base
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                auth_value_methods(:jwks_set)
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                auth_server_route(:jwks) do |r|
         
     | 
| 
      
 13 
     | 
    
         
            +
                  before_jwks_route
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  r.get do
         
     | 
| 
      
 16 
     | 
    
         
            +
                    json_response_success({ keys: jwks_set }, true)
         
     | 
| 
      
 17 
     | 
    
         
            +
                  end
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                private
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                def oauth_server_metadata_body(path = nil)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  metadata = super
         
     | 
| 
      
 24 
     | 
    
         
            +
                  metadata.merge!(jwks_uri: jwks_url)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  metadata
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                def jwks_set
         
     | 
| 
      
 29 
     | 
    
         
            +
                  @jwks_set ||= [
         
     | 
| 
      
 30 
     | 
    
         
            +
                    *(
         
     | 
| 
      
 31 
     | 
    
         
            +
                      unless oauth_jwt_public_keys.empty?
         
     | 
| 
      
 32 
     | 
    
         
            +
                        oauth_jwt_public_keys.flat_map { |algo, pkeys| Array(pkeys).map { |pkey| jwk_export(pkey).merge(use: "sig", alg: algo) } }
         
     | 
| 
      
 33 
     | 
    
         
            +
                      end
         
     | 
| 
      
 34 
     | 
    
         
            +
                    ),
         
     | 
| 
      
 35 
     | 
    
         
            +
                    *(
         
     | 
| 
      
 36 
     | 
    
         
            +
                      unless oauth_jwt_jwe_public_keys.empty?
         
     | 
| 
      
 37 
     | 
    
         
            +
                        oauth_jwt_jwe_public_keys.flat_map do |(algo, _enc), pkeys|
         
     | 
| 
      
 38 
     | 
    
         
            +
                          Array(pkeys).map do |pkey|
         
     | 
| 
      
 39 
     | 
    
         
            +
                            jwk_export(pkey).merge(use: "enc", alg: algo)
         
     | 
| 
      
 40 
     | 
    
         
            +
                          end
         
     | 
| 
      
 41 
     | 
    
         
            +
                        end
         
     | 
| 
      
 42 
     | 
    
         
            +
                      end
         
     | 
| 
      
 43 
     | 
    
         
            +
                    )
         
     | 
| 
      
 44 
     | 
    
         
            +
                  ].compact
         
     | 
| 
      
 45 
     | 
    
         
            +
                end
         
     | 
| 
      
 46 
     | 
    
         
            +
              end
         
     | 
| 
      
 47 
     | 
    
         
            +
            end
         
     |