Class: TrackRelay::ClientId::SignedToken

Inherits:
Object
  • Object
show all
Defined in:
lib/track_relay/client_id/signed_token.rb

Overview

First-party, HMAC-signed client token — the spoof-proof replacement for _ga-cookie browser proof.

The cookie value is generated and verified with ActiveSupport::MessageVerifier (SHA256), so any tampering, truncation, or wrong-secret forgery verifies to nil rather than raising.

Defined Under Namespace

Classes: Token

Constant Summary collapse

"_tr_cid"

Class Method Summary collapse

Class Method Details

.from_request(request, secret:) ⇒ String?

Convenience for gate/client-id use: just the verified client_id.

Parameters:

  • request (#cookies, nil)

    any object exposing request.cookies[COOKIE_NAME]

  • secret (String)

    HMAC signing secret

Returns:

  • (String, nil)

    the embedded client_id, or nil when the cookie is missing or unverifiable.



70
71
72
# File 'lib/track_relay/client_id/signed_token.rb', line 70

def self.from_request(request, secret:)
  read(request, secret: secret)&.client_id
end

.mint(secret:, client_id:, session_id: nil, session_started_at: nil, last_seen_at: nil, ttl: nil) ⇒ String

Generate a signed cookie value embedding the client_id and optional session fields.

Parameters:

  • secret (String)

    HMAC signing secret

  • client_id (String)

    GA4-shaped client id to embed

  • session_id (String, nil) (defaults to: nil)

    GA4 session id

  • session_started_at (Time, nil) (defaults to: nil)

    start of the embedded session

  • ttl (ActiveSupport::Duration, nil) (defaults to: nil)

    validity window; nil mints a non-expiring token

Returns:

  • (String)

    opaque signed cookie value



31
32
33
34
35
36
37
38
39
# File 'lib/track_relay/client_id/signed_token.rb', line 31

def self.mint(secret:, client_id:, session_id: nil, session_started_at: nil, last_seen_at: nil, ttl: nil)
  payload = {
    "cid" => client_id,
    "sid" => session_id,
    "sst" => session_started_at&.to_i,
    "lsa" => last_seen_at&.to_i
  }.compact
  verifier(secret).generate(payload, expires_in: ttl)
end

.read(request, secret:) ⇒ Token?

Read and verify the signed cookie into a Token.

Parameters:

  • request (#cookies, nil)

    any object exposing request.cookies[COOKIE_NAME]

  • secret (String)

    HMAC signing secret

Returns:

  • (Token, nil)

    verified token contents, or nil when the cookie is missing or unverifiable.



48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/track_relay/client_id/signed_token.rb', line 48

def self.read(request, secret:)
  value = request&.cookies&.[](COOKIE_NAME)
  return nil if value.nil? || value.empty?

  payload = verifier(secret).verified(value)
  return nil unless payload

  Token.new(
    client_id: payload["cid"],
    session_id: payload["sid"],
    session_started_at: payload["sst"] && Time.at(payload["sst"]).utc,
    last_seen_at: payload["lsa"] && Time.at(payload["lsa"]).utc
  )
end