Basic Meteor Authentication in Phoenix

Written by Pete Corey on Nov 14, 2016.

A question that often comes up when I’m talking to Meteor developers about transitioning to Phoenix is how to handle authentication.

When transitioning, a developer with an existing application and data may want to integrate with Meteor’s existing authentication data in their Elixir/Phoenix application instead of jumping ship and switching to an entirely different authentication scheme.

Let’s dig into how Meteor’s password authentication works and how to use it within a Elixir/Phoenix application.

Setting Up Our Projects

To start, let’s assume that you have a Meteor application built with user accounts managed through the accounts-password package.

For development purposes, let’s assume that your Meteor server is running locally on port 3000, and your MongoDB database instance is running locally on port 3001.

If you want to follow along, a quick way to set this up would be to clone the example Todos application and spin it up on your machine:


git clone https://github.com/meteor/todos
cd todos
meteor

Next, register a dummy user account (e.g., "user@example.com"/"password") in your browser.


Now that Meteor has MongoDB running and populated with a Meteor-style user account, we’ll set up a new Phoenix project.

We’ll use Mix to create our application, and because we’re using MongoDB as our database, we’ll specify that we don’t want to use Ecto:


mix phoenix.new meteor_auth --no-ecto

Following the instructions in the mongodb driver package, we’ll add dependencies on the mongodb and poolboy packages, and create a MongoPool module.

Finally, we’ll add the MongoPool to our list of supervised worker processes:


children = [
  # Start the endpoint when the application starts
  supervisor(MeteorAuth.Endpoint, []),
  # Here you could define other workers and supervisors as children
  worker(MongoPool, [[database: "meteor", port: 3001]])
]

After restarting our Phoenix server, our application should be wired up and communicating with our local MongoDB database.

Anatomy of Meteor Authentication

At first glance, Meteor’s password-based authentication system can be confusing.

However, once you untangle the mess of asynchronous, highly configurable and pluggable code, you’re left with a fairly straight-forward authentication process.

Authenticating an existing user usually begins with a call to the "login" Meteor method. This method will call the login handler registered in the accounts-password package, which simply does a password check. The result of the password check is passed into the _attemptLogin function, which actually logs the user in if the password check was successful, or returns an error if the check was unsuccessful.

The results of a successful login are that the authenticated user will be associated with the current connection, and that the user’s _id, resume token, and a tokenExpires timestamp will be returned to the client.

Building an Accounts Module

To support the ability to log into a Meteor application through Elixir, we’ll build a (hugely simplified) accounts module. The module will be responsible for transforming the email and password combination passed to the server into an authenticated user session.

Let’s start by defining the module and the module’s entry points:


defmodule MeteorAuth.Accounts do

  def login(socket, %{
              "user" => %{"email" => email},
              "password" => password
            }) when is_binary(email) and is_binary(password) do
    socket
    |> attempt_login(%{query: %{"emails.0.address": email}}, password)
  end

end

The login function in our MeteorAuth.Accounts module will take in a Phoenix channel socket and a map that holds the user’s provided email address and password.

Notice that we're asserting that both email and password should be "binary" types? This helps prevent NoSQL injection vulnerabilities.

The login function calls attempt_login, which grabs the user from MongoDB based on the constructed query (get_user_from_query), checks the user’s password (valid_credentials?), and finally attempt to log the user in (log_in_user):


defp attempt_login(socket, %{query: query}, password) do
  user = get_user_from_query(query)
  valid? = valid_credentials?(user, password)
  log_in_user(valid?, socket, user)
end

To fetch the user document from MongoDB, we’re running a find query against the "users" collection, transforming the resulting database cursor into a list, and then returning the first element from that list:


defp get_user_from_query(query) do
  MongoPool
  |> Mongo.find("users", query)
  |> Enum.to_list
  |> List.first
end

To check the user’s password, we transform the user-provided password string into a format that Meteor’s accounts package expects, and then we use the Comeonin package to securely compare the hashed version of the password string with the hashed password saved in the user’s document:


defp valid_credentials?(%{"services" => %{"password" => %{"bcrypt" => bcrypt}}},
                        password) do
  password
  |> get_password_string
  |> Comeonin.Bcrypt.checkpw(bcrypt)
end

Notice how we’re using pattern matching to destructure a complex user document and grab only the fields we care about. Isn't Elixir awesome?

Before Bcrypt hashing a password string, Meteor expects it to be SHA256 hashed and converted into a lowercased base16 (hexadecimal) string. This is fairly painless thanks to Erlang’s :crypto library:


defp get_password_string(password) do
  :crypto.hash(:sha256, password)
  |> Base.encode16
  |> String.downcase
end

Our valid_credentials? function will return either a true or a false if the user-provided credentials are correct or incorrect.

We can pattern match our log_in_user function to do different things for valid and invalid credentials. If a user has provided a valid email address and password, we’ll log them in by assigning their user document to the current socket:


defp log_in_user(true, socket, user) do
  auth_socket = Phoenix.Socket.assign(socket, :user, user)
  {:ok, %{"id" => user["_id"]}, auth_socket}
end

For invalid credentials, we’ll simply return an error:


defp log_in_user(false, _socket, _user) do
  {:error}
end

Logging in Through Channels

Now that our MeteorAuth.Accounts module is finished up, we can wire it up to a Phoenix channel to test the end-to-end functionality.

We’ll start by creating a "ddp" channel in our default UserSocket module:


channel "ddp", MeteorAuth.DDPChannel

In our MeteorAuth.DDPChannel module, we’ll create a "login" event handler that calls our MeteorAuth.Accounts.login function:


def handle_in("login", params, socket) do
  case MeteorAuth.Accounts.login(socket, params) do
    {:ok, res, auth_socket} ->
      {:reply, {:ok, res}, auth_socket}
    {:error} ->
      {:reply, {:error}, socket}
  end
end

If login returns an :ok atom, we’ll reply back with an :ok status and the results of the login process (the user’s _id).

If login returns an :error, we’ll reply back to the client with an error.

To make sure that everything’s working correctly, we can make another event handler for a "foo" event. This event handler will simply inspect and return the currently assigned :user on the socket:


def handle_in("foo", _, socket) do
  user = socket.assigns[:user] |> IO.inspect
  case user do
    nil ->
      {:reply, :ok, socket}
    %{"_id" => id} ->
      {:reply, {:ok, %{"id" => id}}, socket}
  end
end

On the client, we can test to make sure that everything’s working as expected by running through a few different combinations of "foo" and "login" events:


let channel = socket.channel("ddp", {})
channel.join()

channel.push("foo")
    .receive("ok", resp => { console.log("foo ok", resp) })
    .receive("error", resp => { console.log("foo error", resp) })

...

channel.push("login", {user: {email: "user@example.com"}, password: "password"})
    .receive("ok", resp => { console.log("login ok", resp) })
    .receive("error", resp => { console.log("login error", resp) })

channel.push("foo")
    .receive("ok", resp => { console.log("foo ok", resp) })
    .receive("error", resp => { console.log("foo error", resp) })

And as expected, everything works!

We can now check if a user is currently authenticated on a socket by looking for the assigned :user. If none exists, the current user is unauthenticated. If :user exists, we know that the current user has been authenticated and is who they say they are.

Future Work

So far, we’ve only been able to log in with credentials set up through a Meteor application. We’re not creating or accepting resume tokens, and we’re missing lots of functionality related to signing up, logging out, resetting passwords, etc…

If your goal is to recreate the entirety of Meteor’s accounts package in Elixir/Phoenix, you have a long march ahead of you. The purpose of this article is to simply show that it’s possible and fairly painlessly to integrate these two stacks together.

It’s important to know that for green-field projects, or projects seriously planning on doing a full Elixir/Phoenix transition, there are better, more Phoenix-centric ways of approaching and handling user authentication and authorization.

That being said, if there’s any interest, I may do some future work related to resume tokens, signing up and out, and potentially turning this code into a more full-fledged Elixir package.

For now, feel free to check out the entire project on GitHub to get the full source. Let me know if there’s anything in particular you’d like to see come out of this!

Phoenix Todos - Authorized Sockets

This post is written as a set of Literate Commits. The goal of this style is to show you how this program came together from beginning to end.

Each commit in the project is represented by a section of the article. Click each section's header to see the commit on Github, or check out the repository and follow along.

Written by Pete Corey on Nov 9, 2016.

Authenticated Sockets

Now that we’ve implemented the bulk of the unauthenticated functionality in our application, we need to turn our attention to authenticated functionality.

To do that, we’ll need to authenticate the channel we’re using to communicate with the client. We can do this with a custom connect function that verified the user’s provided guardian_token:


def connect(%{"guardian_token" => jwt}, socket) do
  case sign_in(socket, jwt) do
    {:ok, authed_socket, guardian_params} ->
      {:ok, authed_socket}
    _ ->
      {:ok, socket}
  end
end

If the user is authenticated correctly, we’ll swap their socket out for an auth_socket, which can be used to access the current user’s object and claims.

If the user doesn’t provide a guardian_token, we’ll fall back to our old connect function:


  def connect(_params, socket) do
    {:ok, socket}
  end

All of the old functionality will continue to work as expected.

web/channels/user_socket.ex

... use Phoenix.Socket + import Guardian.Phoenix.Socket ... # performing token verification on connect. + def connect(%{"guardian_token" => jwt}, socket) do + case sign_in(socket, jwt) do + {:ok, authed_socket, _guardian_params} -> + {:ok, authed_socket} + _ -> + {:ok, socket} + end + end + def connect(_params, socket) do

Guardian Token

Now that our socket connection is expecting a guardian_token parameter, we need to supply it in our connectSocket action.

web/static/js/actions/index.js

... params: { - token: jwt + guardian_token: jwt }

Connect Socket Thunk

Because our entire socket connection will be authenticated or unauthenticated, we need to prepare ourselves to re-establish the connection every time we log in/out.

To start, we’ll need to clear out our local set of lists every time we connect to the socket.

We also need to trigger a call to our joinListsChannel thunk every time we connect.

web/static/js/actions/index.js

... export function connectSocket(jwt) { - let socket = new Socket("/socket", { - params: { - guardian_token: jwt - } - }); - socket.connect(); - return { type: CONNECT_SOCKET, socket }; + return (dispatch, getState) => { + let socket = new Socket("/socket", { + params: { + guardian_token: jwt + } + }); + socket.connect(); + dispatch({ type: CONNECT_SOCKET, socket }); + dispatch(joinListsChannel("lists.public")); + }; }

web/static/js/app.js

... store.dispatch(connectSocket(store.getState().jwt)); -store.dispatch(joinListsChannel("lists.public"));

web/static/js/reducers/index.js

... case CONNECT_SOCKET: - return Object.assign({}, state, { socket: action.socket }); + return Object.assign({}, state, { socket: action.socket, lists: [] }); case JOIN_LISTS_CHANNEL_SUCCESS:

Reconnect

Now we’ll reconnect to our socket every time a user signs in, signs up, or signs out. This ensures that the socket connection is always properly authenticated.

These changes also introduced a few small bugs which we quickly fixed.

web/static/js/actions/index.js

... dispatch(signUpSuccess(res.user, res.jwt)); + dispatch(connectSocket(res.jwt)); return true; ... dispatch(signOutSuccess()); + dispatch(connectSocket(res.jwt)); return true; ... dispatch(signInSuccess(res.user, res.jwt)); + dispatch(connectSocket(res.jwt)); return true;

web/static/js/layouts/App.jsx

... import { signOut, createList } from "../actions"; +import _ from "lodash"; ... const list = _.find(this.props.lists, list => list.id == this.props.params.id); - if (list.user_id) { + if (list && list.user_id) { const publicList = _.find(this.props.list, list => !list.user_id);

Fetching All Lists

To make things simpler, and to show off a feature of Phoenix, I’ve decided to merge the "lists.public" and "lists.private" publications into a single channel: "lists".

This channel will return all lists accessible by the current user, based on their authenticated socket.

We replaced the List.public function with List.all, which takes in a user_id. When user_id is nil, we return all public lists, as before. However, when user_id isn’t nil, we return all lists owned by that user (^user_id == list.user_id), and all public lists.

test/models/list_test.exs

... - test "public" do + test "all" do user = User.changeset(%User{}, %{ ... }) - Repo.insert!(%List{ + |> Repo.preload(:todos) + private = Repo.insert!(%List{ name: "private", ... }) + |> Repo.preload(:todos) - lists = List |> List.public |> Repo.all + public_lists = List |> List.all(nil) |> Repo.all |> Repo.preload(:todos) + all_lists = List |> List.all(user.id) |> Repo.all |> Repo.preload(:todos) - assert lists == [public] + assert public_lists == [public] + assert all_lists == [public, private] end

web/channels/list_channel.ex

... - def join("lists.public", _message, socket) do - lists = List |> List.public |> Repo.all + defp get_user_id(socket) do + case Guardian.Phoenix.Socket.current_resource(socket) do + user -> + user.id + _ -> + nil + end + end + + def join("lists", _message, socket) do + lists = List |> List.all(get_user_id(socket)) |> Repo.all {:ok, lists, socket}

web/channels/user_socket.ex

... # channel "rooms:*", PhoenixTodos.RoomChannel - channel "lists.public", PhoenixTodos.ListChannel + channel "lists", PhoenixTodos.ListChannel

web/models/list.ex

... - def public(query) do + def all(query, nil) do from list in query, ... + def all(query, user_id) do + from list in query, + where: ^user_id == list.user_id or is_nil(list.user_id), + order_by: list.inserted_at, + preload: [:todos] + end + def findByName(query, name) do

web/static/js/actions/index.js

... dispatch({ type: CONNECT_SOCKET, socket }); - dispatch(joinListsChannel("lists.public")); + dispatch(joinListsChannel("lists")); };

Final Thoughts

In hashing out this authorization scheme, I’ve realized there are lots of problems with this approach. Splitting communication across both WebSockets and REST endpoints creates lots of confusion around a user’s authorization state.

In hindsight, it would have been better to do everything over WebSockets and forget the REST user and sessions endpoints altogether. I’ll be sure to write up my thoughts around the problems with this kind of authorization and how to to it better in the future.

Next week, we should be able to finish up all authenticated functionality and finish up the Meteor to Phoeix migration project!

NoSQL Injection in Phoenix Applications

Written by Pete Corey on Nov 7, 2016.

NoSQL injection is a class of application vulnerability where a malicious user can inject control structures into a query against a NoSQL database. MongoDB is the usual victim in these types of attacks, for reasons we’ll discuss towards the end of the article.

Coming most recently from a Meteor background, NoSQL injection is no stranger to me. It’s one of the most prevalent vulnerabilities I find during Meteor security assessments.

Interestingly, as I’ve been diving headfirst into Elixir and the Phoenix framework, I’ve been seeing the NoSQL injection monster raising its ugly head.

Let’s take a look at what NoSQL injection actually is, what it looks like in an Elixir/Phoenix application, and how it can be prevented.

What is NoSQL Injection

NoSQL injection is an interesting vulnerability that’s especially prevalent in systems built with MongoDB. NoSQL injection can occur when a user’s unvalidated, unsanitized input is inserted directly into a MongoDB query object.

To make things more real, let’s demonstrate NoSQL injection with a (slightly contrived) example.

Imagine you have a Phoenix channel that removes shopping cart “item” documents from a MongoDB collection whenever it receives an "empty_cart" event:


def handle_in("empty_cart", cart_id, socket) do
  MongoPool
  |> Mongo.delete_many("items", %{"cart_id" => cart_id})
  {:noreply, socket}
end

This code is making the assumption that the "empty_cart" channel event will always be invoked with cart_id as a string. However, it’s important to realize that cart_id can be any JSON-serializable type.

What would happy if a malicious user passed in {$gte: ""} as a cart_id? Our resulting MongoDB query would look like this:


Mongo.delete_many("items", %{"cart_id" => %{"$gte" => ""}})

This query would remove every item document in the database.

Similar types of attacks can be used to fetch large amounts of unauthorized data from find and findOne queries.


Even more dangerously (and more contrived), let’s imagine we have a channel event handler that lets users search through their cart items for items matching a user-provided key/value pair:


def handle_in("find_items", %{"key" => key, "value" => value}, socket) do
  items = MongoPool
  |> Mongo.find("items", %{
       key => value,
       "user_id" => socket.assigns[:user]._id
     })
  |> Enum.to_list
  {:reply, {:ok, items}, socket}
end

This seems relatively safe. We’re assuming that the user will pass in values like "foo"/"bar" for "key" and "value", respectively.

However, what would happen if a malicious user passed in "$where" and "d = new Date; do {c = new Date;} while (c - d < 10000);" as a "key"/"value" pair?

The resulting MongoDB query would look like this:


Mongo.find("items", %{
  "$where" => "d = new Date; do {c = new Date;} while (c - d < 10000);",
  "user_id" => socket.assigns[:user].id
})

By exploiting the $where operator in this way, the malicious user could peg the CPU of the server running the MongoDB instance at 100% for ten seconds per document in the collection, preventing any other queries from executing during that time.

This malicious elixir loop could easily be modified to run indefinitely, requiring you to either kill the query manually, or restart your database process.

How to Prevent It

Preventing this flavor of NoSQL injection is fairly straight-forward. You simply need to make assertions about the types of your user-provided data.

If you’re expecting cart_id to be a string, make sure it’s a string before working with it.

In Elixir, this type of type checking can be neatly accomplished with pattern matching. We can patch up our first example with a simple pattern match that checks the type of cart_id:


def handle_in("empty_cart", cart_id, socket) when is_binary(cart_id) do
  MongoPool
  |> Mongo.delete_many("items", %{"cart_id" => cart_id})
  {:noreply, socket}
end

The when is_binary(cart_id) guard expression asserts that cart_id is a binary type (i.e., a string) before pattern matching on this instance of the handle_in function.

If a malicious user passed in %{"$gte" => ""} for an cart_id, this version of our "empty_cart" handler would not be evaluated, preventing the possibility of NoSQL injection.


Our "find_items" example is also susceptible to query objects being passed in as value, and would benefit from guard clauses.

However, the fundamental flaw with this example is that user input is being directly used to construct a root level MongoDB query.

A better version of our "find_items" channel event handler might look something like this:


def build_query("name", value), do: %{ "name" => value }
def build_query("category", value), do: %{ "category" => value }

def handle_in("find_items",
              %{"key" => key,
                "value" => value},
              socket) when is_binary(key) and is_binary(value)
  query = build_query(key, value)
  |> Map.put("user_id", socket.assigns[:user]._id
  items = MongoPool
  |> Mongo.find("items", query)
  |> Enum.to_list
  {:reply, {:ok, items}, socket}
end

By mapping between the provided key value and a list of known MongoDB query objects, we know that nothing can be injected into the root of our query.

Alternatively, we can continue to use the raw value of key to construct our query, but we can add a key in ["name", "category"] guard clause to our handle_in function to assert that the user is only searching over the "name" or "category" fields:


def handle_in("find_items",
              %{"key" => key,
                "value" => value},
              socket) when key in ["name", "category"] and is_binary(value)

By preventing malicious users from controlling the root level of our MongoDB query, we can prevent several types of nasty NoSQL injection vulnerabilities within our application.


That being said, the best way to prevent these kinds of injection attacks is to use a query builder, like Ecto.

Unfortunately, as we discussed last week, the Mongo.Ecto adapter is currently in a state of flux and does not play nicely with Ecto 1.1 or Ecto 2.0.

Picking on MongoDB

This type of NoSQL injection mostly applies to applications using MongoDB. This is because MongoDB has made the “interesting” design decision to intermix query control structures and query data in a single query object.

If a malicious user can inject data into this object, they can potentially inject query control structures as well. This is the fundamental idea behind NoSQL injection.

Looking at other NoSQL databases, it becomes apparent that MongoDB is alone in making this design decision.

Redis, for example, is a much simpler solution overall. Redis doesn’t mix data and control structures. The query type is specified up-front, almost always by the application, and unescapable data follows.

As another example, CouchDB lets developers build custom queries through “views”, but these views are written in advance and stored on the server. They can’t be modified at runtime, let alone modified by a malicious user.

There are already a host of compelling reasons not to use MongoDB. I would add MongoDB’s decision to intermix data and control structures to this ever growing list.

Final Thoughts

While MongoDB does have its short-comings, it’s important to realize that it’s still being used extensively in the Real World™. In fact, MongoDB is the most popular NoSQL database, standing heads and shoulders above its competition in usage statistics.

For this reason, it’s incredibly important to understand MongoDB-flavored NoSQL injection and how to prevent it in your applications.

For more information on NoSQL injection, check out the “NoSQL Injection in Modern Web Applications” presentation I gave at last year’s Crater Conference, and be sure to grab a copy of my “Five Minute Introduction to NoSQL Injection”.