Tracking Anonymous/Unauthorized Users with Elixir/Phoenix Channels and Presence

Peter Richards
Aug 23, 2016 · 4 min read


  • We want authorized users to be able to read/write over a socket
  • We want unauthorized (anonymous) users to be able to view what’s happening in a given room but not participate.
  • Display authorized users in a list, one per row.
  • Display how many “Anonymous” users are in the room but since they are less interesting, instead of displaying each one on it’s own line, we want to combine them into one line item, as a count (i.e. “Anonymous: 23”).

DISCLAIMER: I’m assuming you know how to use Phoenix.Channels and Phoenix.Presence the normal way. I will omit some code that may be necessary to get Presence working. I am using Guardian/Ueberauth for authentication (I used this example project to implement it: I’m also fairly new to Elixir/Phoenix, so this may not be best practice, let me know if there’s something that can be improved.

Here’s how I did it. I’m sure there are plenty of ways to improve it (let me know), but it works.


def join(“rooms:” <> id, %{“guardian_token” => token}, socket) do
socket = case sign_in(socket, token) do
{:ok, authed_socket, _guardian_params} -> authed_socket
{:error, reason} -> socket
case Repo.get(Room, id) do
%Room{} = room ->
send(self, :after_join)
user = current_resource(socket) || %User{}
socket = assign(socket, :user, user)
{:ok, socket}
nil -> {:error, %{reason: “Room does not exist”}}

Here we check for two things. Is a User present, and is the requested Room valid. The first section is straight from Guardian/Ueberauth. If the token is present, sign_in/2 will add stuff to the socket to allow us to grab the user later, if not, we just keep the original socket. We then check that the requested Room exists, if so, we send ourselves an :after_join message (explained below). And assign a User to the socket. ```current_resource/1``` is from Guardian, it will return a User or nil. I like to stick an empty User in the socket instead of nil, because this allows us to treat it like a User, and access fields(, user.username, etc.) without having to check if it’s a valid User or nil.

Next we want to block these “Anonymous” users from pushing stuff into the socket (obviously some client-side checks should be in place too).

def handle_in(_, _, %{assigns: %{user: %User{id: nil}}} = socket) do
{:reply, {:error, %{reason: “Not signed in”}}, socket}

Here we just match any inbound messages from anon users, and reject them. There may be situations where you want to accept some stuff from anon users, so you’ll have to do something else here.

Let’s look at handling Presence in the :after_join

def handle_info(:after_join, socket) do
Presence.track(socket,, %{
online_at: :os.system_time(:milli_seconds),
username: socket.assigns.user.username
push socket, “presence_state”, Presence.list(socket)
{:noreply, socket}

Pretty straight forward. You can add other stuff here too, like sending the room’s current messages state, etc. I’ve stripped that stuff out for the example, but here’s a good place to do that.

Since we have a empty User struct (%User{}) instead of just nil, we can call, and user.username for all sockets, the signed in users will have valid values, the anon users will be nil.

The way this gets represented by Presence is as follows (2 authorized users, 2 anon users):

%{    “” => %{metas: [
%{username: nil, online_at: 123, phx_ref: “abc”},
%{username: nil, online_at: 123, phx_ref: “def”}
“11” => %{metas: [
%{username: “test_user”, online_at: 123, phx_ref: “ghi”}
“12” => %{metas: [
%{username: “test_user2”, online_at: 123, phx_ref: “jkl”}

Regular users will have a id key, and an array with one item (it can be longer if the user is using the service from multiple sources). And the empty key (anon users) will have one entry for each anon user. There may be a reason you don’t want this to be an empty string (I’ve had no issues with it), in which case you can either check the user’s id before adding it to Presence, or initialize the anon users as %User{id: :anonymous}

That covers everything from the server side. Let’s look at the client side.


listBy(user_id, {metas: metas}) {
if (!user_id) {
return {
username: “Anonymous”,
count: metas.length
return {
username: metas[0].username,
renderAll() {
var allUsers = Presence.list(this.presences, this.listBy);
var anonymousUsers = $.grep(allUsers, function(item){
return item.username == “Anonymous”;
})[0] || {username: “Anonymous”, count: 0};
var authorizedUsers = $.grep(allUsers, function(item){
return item != anonymousUsers
this.$listEl.html( => `
`).join(“”) + `
<li>${anonymousUsers.username}: ${anonymousUsers.count}</li>

This is pretty straight forward. In our listBy method, we check to see if the user_id is present (indicating a authorized user), if it’s not, then we count the metas array, if it is present, we grab the fields we want to display.

We then grep the list and grab the anonymous users, grep again to grab the authorized users, then we render the HTML and stick it in the DOM.

It should output like this:

  • test_user
  • test_user2
  • Anonymous: 2

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store