Tutorial
Set session values from LiveView
Basically, every web application has a need to store session values for the user visiting the page. At least with some sort of authentication, you store an authentication token or current user ID so you can retrieve the user for every request. There are also other use cases like preferences regarding language or dark mode. In a live view application, which mainly communicates over web sockets, you can't set session values directly. However, as you can see here, when I click the Set Session button and refresh the page, the value is still accessible through the user's browser session. So in this video, I will go through how you can easily add this in an elegant way.
Setting Up the StoreSession Controller
As mentioned, I can't set a session value directly from the live view. I need to create a controller that I can post to, which in turn can set a session value for the user. The controller supports both encrypted and non-encrypted params, though I strongly recommend using encryption in production:
defmodule TutorialWeb.StoreSessionController do
use TutorialWeb, :controller
def create(conn, %{"encrypted" => token}) do
updated_conn =
case decrypt(token) do
{:ok, params} ->
Enum.reduce(params, conn, fn {key, value}, acc_conn ->
put_session(acc_conn, key, value)
end)
_ ->
conn
end
send_resp(updated_conn, 200, "")
end
# Note: For development only. Use encryption in production
def create(conn, params) do
updated_conn = Enum.reduce(params, conn, fn {key, value}, acc_conn ->
put_session(acc_conn, String.to_existing_atom(key), value)
end)
send_resp(updated_conn, 200, "")
end
defp decrypt(token) do
Phoenix.Token.decrypt(TutorialWeb.Endpoint, "secret_key", token, max_age: 600)
end
end
Note that for security reasons, we use String.to_existing_atom(key)
. We do that for security reasons, so this means that the atom needs to have been defined before. Also, for security, consider:
- Use a whitelist of allowed session keys
- Store the secret key in config
- Adjust the token max age based on your needs
Adding the Event Listener
Even though I added the controller, we can't post to it directly. Instead, we can dispatch a JavaScript event from inside the live view. So here in the app JavaScript file, I will add an event listener that listens for these events, takes the payload, and posts it to the new store session route:
window.addEventListener("phx:store-session", (event) => {
const token = document.querySelector('meta[name="csrf-token"]').getAttribute('content')
fetch('/store-session', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': token
},
body: JSON.stringify(event.detail)
})
})
One thing to note here is that I grabbed the CSRF token from the meta tag in the DOM and use it as a header when posting. That is a way for Phoenix to verify that the post is legitimate.
Configuring a Route for Session Storage
Before I can post to the controller, I need to set up a route for it. Depending on your app and your security concerns, you can put this behind an authenticated pipeline:
scope "/", TutorialWeb do
pipe_through :browser
post "/store-session", StoreSessionController, :create
end
LiveView Event Trigger Example
The final piece of the puzzle is triggering the session update from LiveView. By emitting a custom event, we can communicate with our JavaScript hook to initiate the session storage process. Here’s an example of how this is accomplished in LiveView:
def handle_event("demo", _param, socket) do
{
:noreply,
socket
|> push_event("store-session", %{encrypted: encrypt(%{my_key: "My Value"})})
}
end
defp encrypt(payload) do
Phoenix.Token.encrypt(TutorialWeb.Endpoint, "secret_key", payload)
end
Conclusion
So now we have overcome one of the limitations in Phoenix LiveView regarding session management. By combining a small amount of JavaScript with a custom controller, we can securely set session values directly from LiveView. I hope this helps you in your application.