Gates WebSocket connections, which bypass Limen.Plug.
Phoenix dispatches sockets before any plug in the endpoint runs, so a client that never passed the HTTP gate could still open a LiveView or channel socket. To close that gap:
- when rendering a page, embed a socket token issued for the request
(
token/2), for example in a meta tag; - send it back as a connect parameter (
_limenby default); - check it when the socket connects, with
check/3in a socket'sconnect/3callback or with theLimen.LiveViewon_mounthook.
Tokens are signed with the keys of an instance, and must be checked by the same instance.
The token is signed with its own key and bound to the client's identity
like a pass cookie (by default its prefix, JA4 and user agent, see the
:bind option of Limen.Challenge), but it is not a pass: it cannot be
used as a cookie. A client whose identity changed since the page was
served fails the check, reloads the page, and goes through the HTTP gate
again.
Checking needs the peer address, x- headers (for x-forwarded-for and
the JA4 header behind a proxy) and the user agent:
socket "/live", Phoenix.LiveView.Socket,
websocket: [connect_info: [:peer_data, :x_headers, :user_agent, :uri, session: @session]]Each check is a decision at the :socket stage, emitted and sampled like
any other. In dry-run mode failed checks are reported and let through.
Summary
Functions
Checks a socket connection.
The connect parameter the token is expected in.
Issues a socket token for the client of conn.
Functions
@spec check(map(), map(), keyword()) :: {:ok, Limen.Decision.t()} | {:error, Limen.Decision.t()}
Checks a socket connection.
connect_info is what Phoenix passes to connect/3 (see the module
documentation for the keys it needs) and params the connect parameters.
Options
:instanceor:otp_app- the instance checking the token, as forLimen.Plug. Required.:mode-:dry_runor:enforce, overriding the instance's mode.:facts- facts about the connection, as forLimen.put_facts/2, recorded in the decision.:policy- aLimen.Policywhosetrustrules are checked first, with the facts: a trusted connection is allowed at the:truststage, whatever its token and even when its prefix is banned, as the same client's requests are byLimen.Plug.
Returns {:ok, decision} when the connection may proceed (always, in
dry-run mode) and {:error, decision} when it must be refused.
@spec param() :: String.t()
The connect parameter the token is expected in.
@spec token(Plug.Conn.t(), keyword()) :: String.t() | nil
Issues a socket token for the client of conn.
Returns nil when Limen denied the request, in which case the page is
not being rendered anyway.
Options
:instanceor:otp_app- the instance issuing the token. Defaults to the instance that made the decision for the request, and must be given whenLimen.Plugmade none (on:offand:trackroutes).