Module: WhopSDK::Helpers::VerifyUserToken
- Defined in:
- lib/whop_sdk/helpers/verify_user_token.rb
Overview
Verifies Whop-issued x-whop-user-token JWTs. By default, fetches the public signing keys from Whop’s canonical JWKS endpoint and caches them at module scope (TTL-bounded with a cooldown on refetch). The behavior mirrors the TypeScript SDK’s ‘createRemoteJWKSet` path so that upgrading to a key rotation doesn’t require a gem release.
Defined Under Namespace
Classes: RemoteJwks, UserTokenPayload
Constant Summary collapse
- USER_TOKEN_HEADER_NAME =
"x-whop-user-token"- DEFAULT_JWKS_URL =
"https://api.whop.com/.well-known/jwks.json"- TOKEN_ISSUER =
"urn:whopcom:exp-proxy"- TOKEN_ALGORITHM =
"ES256"- JWKS_CACHE_MAX_AGE_SECONDS =
12h freshness window before a proactive refresh; 30s cooldown between refetches when a kid lookup misses. Matches the TS SDK (‘cacheMaxAge: 12 * 60 * 60 * 1000, cooldownDuration: 30_000`).
12 * 60 * 60
- JWKS_COOLDOWN_SECONDS =
30
Class Method Summary collapse
-
.get_user_token(token_or_headers, header_name: nil) ⇒ String?
Extracts the user token from various input types.
- .import_static_key(public_key) ⇒ Object private
- .remote_jwks_for(url) ⇒ Object private
- .reset_jwks_cache! ⇒ Object
-
.verify_user_token!(token_or_headers, app_id: nil, public_key: nil, jwks_url: nil, header_name: nil) ⇒ UserTokenPayload
Verifies a Whop user token.
- .verify_with_remote_jwks(token_string, jwks_url:) ⇒ Object private
- .verify_with_static_key(token_string, public_key:) ⇒ Object private
Class Method Details
.get_user_token(token_or_headers, header_name: nil) ⇒ String?
Extracts the user token from various input types.
120 121 122 123 124 125 126 127 128 129 130 131 |
# File 'lib/whop_sdk/helpers/verify_user_token.rb', line 120 def self.get_user_token(token_or_headers, header_name: nil) header_name ||= USER_TOKEN_HEADER_NAME case token_or_headers when String token_or_headers when Hash token_or_headers[header_name] || token_or_headers[header_name.downcase] || token_or_headers[header_name.upcase] end end |
.import_static_key(public_key) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
222 223 224 225 226 227 228 229 230 231 |
# File 'lib/whop_sdk/helpers/verify_user_token.rb', line 222 def self.import_static_key(public_key) stripped = public_key.to_s.strip if stripped.start_with?("-----BEGIN") OpenSSL::PKey::EC.new(stripped) else JWT::JWK.new(JSON.parse(stripped)).verify_key end rescue JSON::ParserError, OpenSSL::PKey::ECError => e raise StandardError, "Invalid public key provided to verifyUserToken: #{e.}" end |
.remote_jwks_for(url) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
100 101 102 103 104 105 106 107 108 |
# File 'lib/whop_sdk/helpers/verify_user_token.rb', line 100 def self.remote_jwks_for(url) @jwks_cache_mutex.synchronize do @jwks_cache[url] ||= RemoteJwks.new( url, cache_max_age_seconds: JWKS_CACHE_MAX_AGE_SECONDS, cooldown_seconds: JWKS_COOLDOWN_SECONDS ) end end |
.reset_jwks_cache! ⇒ Object
111 112 113 |
# File 'lib/whop_sdk/helpers/verify_user_token.rb', line 111 def self.reset_jwks_cache! @jwks_cache_mutex.synchronize { @jwks_cache.clear } end |
.verify_user_token!(token_or_headers, app_id: nil, public_key: nil, jwks_url: nil, header_name: nil) ⇒ UserTokenPayload
Verifies a Whop user token.
By default fetches the public signing keys from ‘jwks_url` (or the canonical Whop endpoint) and caches them at module scope. Pass `public_key` to verify against a static key instead — useful for self-hosted / test setups where you know the exact key.
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
# File 'lib/whop_sdk/helpers/verify_user_token.rb', line 149 def self.verify_user_token!( token_or_headers, app_id: nil, public_key: nil, jwks_url: nil, header_name: nil ) token_string = get_user_token(token_or_headers, header_name: header_name) if token_string.nil? || token_string.empty? raise StandardError, <<~ERROR Whop user token not found. If you are the app developer, ensure you are developing in the whop.com iframe and have the dev proxy enabled. ERROR end payload = if public_key verify_with_static_key(token_string, public_key: public_key) else verify_with_remote_jwks(token_string, jwks_url: jwks_url || DEFAULT_JWKS_URL) end unless payload["sub"] && payload["aud"] && !payload["aud"].is_a?(Array) raise StandardError, "Invalid user token provided to verifyUserToken" end if app_id && payload["aud"] != app_id raise StandardError, "Invalid app id provided to verifyUserToken" end UserTokenPayload.new(user_id: payload["sub"], app_id: payload["aud"]) end |
.verify_with_remote_jwks(token_string, jwks_url:) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 |
# File 'lib/whop_sdk/helpers/verify_user_token.rb', line 197 def self.verify_with_remote_jwks(token_string, jwks_url:) remote = remote_jwks_for(jwks_url) # ruby-jwt calls the loader twice when the token's kid isn't found # in the current set — once normally, then again with # invalidate: true. The loader uses that signal to force a # cooldown-guarded refresh, mirroring jose's remote JWKS behavior. jwks_loader = ->(opts) { remote.jwk_set(force_refresh: opts[:invalidate]) } decoded_payload, = JWT.decode( token_string, nil, true, algorithms: [TOKEN_ALGORITHM], iss: TOKEN_ISSUER, verify_iss: true, jwks: jwks_loader, # Legacy tokens (pre-kid rollout) have no kid header. Let # ruby-jwt fall back to the first key in the set for those. allow_nil_kid: true ) decoded_payload end |
.verify_with_static_key(token_string, public_key:) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
183 184 185 186 187 188 189 190 191 192 193 194 |
# File 'lib/whop_sdk/helpers/verify_user_token.rb', line 183 def self.verify_with_static_key(token_string, public_key:) key = import_static_key(public_key) decoded_payload, = JWT.decode( token_string, key, true, algorithm: TOKEN_ALGORITHM, iss: TOKEN_ISSUER, verify_iss: true ) decoded_payload end |