Phoenix’ Channels With Coherence authentication
I started exploring Phoenix for one thing only: Channels (or actually fast real time communication over websockets). In this post I explore how to use them (yes this is a follow up of My first Phoenix-app-post).

Preparing for the authentication problem
Websockets don’t pass session cookies. Because we don’t have access to these we need to transfer the user’s identity in a different way. One of the recommendations I found was passing a user_token
using a <meta>
-tag (adjusting templates/layout/app.html.eex
):
<%= if Coherence.current_user(@conn) do %>
<%= tag :meta, name: "user_token", content: Phoenix.Token.sign(@conn, "user", Coherence.current_user(@conn).id) %>
<% end %>
We can access this with a simple query selector in javascript:
document.querySelector("meta[name=user_token]").content
But that’s for later. Let’s move to the server side, since we need something to connect to, a Socket.
Socket
In our default project there is already a user_socket.ex
in the web/channels
folder. Open it and uncomment the following line:
channel "room:*", MyApp.RoomChannel
Then rewrite the connect function to accept a token as a parameter:
def connect(%{"token" => token}, socket) do
case Phoenix.Token.verify(socket, "user", token, max_age: 1209600) do
{:ok, user_id} ->
socket = assign(socket, :user, MyApp.Repo.get!(MyApp.User, user_id))
{:ok, socket}
{:error, _} ->
:error
end
end
Phoenix.Token.verify does all the magic, and turns a given token into a user_id
, allowing us to access the user through Repo.get!
(namespaced by the application name here).
Note As said, I really found this something to get used to: Repo.get!(User, id)
, instead of User.find(id)
Above code is also mentioning a RoomChannel, which we will create next:
defmodule MyApp.RoomChannel do
use Phoenix.Channel
# intercept ["new_post"]
def join("room:" <> _private_room_id, _params, socket) do
{:ok, socket}
end
def handle_in("new_post", %{"message" => message}, socket) do
user = Coherence.current_user(socket)
changeset = MyApp.Post.changeset(%MyApp.Post{user: user}, %{message: message)
{status, post} = MyApp.Repo.insert(changeset)
if status == :ok do
broadcast! socket, "new_post", %{message: post.message, id: post.id, inserted_at: post.inserted_at}
end
{:noreply, socket}
end
# def handle_out("new_post", payload, socket) do
# user = socket.assigns[:user]
# if MyApp.User.mentioned?(payload.message, user) do
# push socket, "new_post", payload
# end
# {:noreply, socket}
# end
end
I’ve commented out the handle_out/3
code here (note /3
depicts that this function has 3 arguments.
When a message enters the room channel the user is retrieved from the socket, a changeset is prepared (like in our controller) and if ok a new message is broadcast in the room.
Note: This message will flow through handle_out
when it matches the intercept
, which allows us to do some checking. In this case the code calls the User.mentioned?/2
-function, which checks whether the logged in user-name is mentioned in the message and only pushes the message to the user when the user is mentioned.
Plugging into the socket, client-side
Foundation comes with the necessary code. Simply uncomment the following line in app.js
:
import socket from "./socket"
And update the code to something along the lines of the following:
import {Socket} from "phoenix"
let socket = null
if (document.querySelector("meta[name=user_token]")) {
socket = new Socket("/socket", {
params: {
token:
document.querySelector("meta[name=user_token]").content
}
})
socket.connect()
// Now that you are connected, you can join channels with a topic:
let channel = socket.channel("room:1", {})
let message = document.querySelector("#post_message")
let messagesContainer = document.querySelector("#posts")
message.addEventListener("keypress", event => {
if(event.keyCode === 13){
channel.push("new_post", {message: message.value})
message.value = null
}
})
channel.on("new_post", post => {
let messageItem = document.createElement("li");
messageItem = post.message
messagesContainer.appendChild(messageItem)
})
channel.join()
.receive("ok", resp => { console.log("Joined successfully", resp) })
.receive("error", resp => { console.log("Unable to join", resp) })
}
export default socket
I made sure I only connect to a socket when a user is actually logged in (when a token is mentioned in the header, this to prevent unneeded traffic).
The listener channel.on(“new_post”…)
displays any messages sent to the room tagged new_post
. The rest is straight forward JavaScript (ES6-style).
We’re relying here on some more dom code, but make sure the page you’re displaying the posts on contains an input field with id post_message
. And a container to be filled with posts with id posts
(in this case a <ol>
), but I’ll leave the very HTML up to you.
Conclusion
After two days of experimenting I’m confident building an actual application in Phoenix / Elixir. While the programming style is something to get used to, its response is fast. There are quite some things I really like about the framework, including the schema definition inclusion and valid parameter parsing in the model. I recommend you to try Phoenix yourself!