Signed does not mean authorized
A signed stream name (two courses ago) can't be tampered with. A client can't invent a new one; it can only present one the server generated. That fact answers one question and leaves another open. Proving a name is genuine says nothing about whether this visitor, right now, should be allowed to hear it.
One key for the whole application
Turbo.signed_stream_verifier, from this app's installed turbo-rails-2.0.23:
def signed_stream_verifier
@signed_stream_verifier ||= ActiveSupport::MessageVerifier.new(signed_stream_verifier_key, digest: "SHA256", serializer: JSON)
end
And where the key comes from, turbo/engine.rb, unedited:
initializer "turbo.signed_stream_verifier_key" do
config.after_initialize do
Turbo.signed_stream_verifier_key = config.turbo.signed_stream_verifier_key ||
Rails.application.key_generator.generate_key("turbo/signed_stream_verifier_key")
end
end
One verifier and one key, derived once from this app's secret_key_base through Rails.application.key_generator, shared by every stream name this app signs. The public roadmap's "catalog_roadmap" and a hypothetical private stream would both be verified by that same key. The key proves this server generated the name. It has no idea who's asking right now, because a single application-wide key was never designed to encode that.
What "verified" checks, once more
Turbo::StreamsChannel#subscribed, read in the "Turbo Streams: WebSockets" course, is the entire authorization check that ships with Turbo:
def subscribed
if stream_name = verified_stream_name_from_params
stream_from stream_name
else
reject
end
end
"Verified" means one thing here: the signature matches the payload, under this app's key. It doesn't mean "the current Action Cable connection belongs to the user this stream was meant for". Nothing in this method reads current_user, checks a session or compares an id. Anyone holding a validly signed stream name, however they got it, subscribes successfully. Turbo was never designed to answer "should this person see this", only "is this name real".
Why this app's one stream gets away with it
"catalog_roadmap", the last two courses' example, is fine because there's nothing to protect. The roadmap is public course data, visible to signed-in and anonymous visitors alike, by design. Signing still matters there (a tampered name could subscribe to something unintended), but authorization was never a question for a stream everyone may see. Once a stream is meant for one person and nobody else, that gap, signed but not authorized, becomes the whole problem this course sets out to close.
Key takeaways
- This app's
Turbo.signed_stream_verifieruses one application-wide key, derived fromsecret_key_base. Every stream name this app signs, public or private, shares that key. Turbo::StreamsChannel#subscribed, read again, checks the signature and nothing else: nocurrent_user, no session check, no per-user logic.- "Verified" proves a name wasn't tampered with. It proves nothing about whether the connection presenting it belongs to the person the stream was meant for.
- This app's one stream,
"catalog_roadmap", is safe without extra authorization only because it's public. The gap in this lesson matters once a stream isn't meant for everyone.
Keep going with lesson 2
Signed does not mean secret
Signing gives integrity without confidentiality: plain Base64.decode64 recovers a stream name's payload without any key.
A free account also gives you the quizzes, your progress and notes, and the discussion.