{"id":11819,"date":"2016-04-08T12:11:24","date_gmt":"2016-04-08T09:11:24","guid":{"rendered":"https:\/\/www.webcodegeeks.com\/?p=11819"},"modified":"2017-04-12T12:36:42","modified_gmt":"2017-04-12T09:36:42","slug":"ridiculously-fast-api-authentication-phoenix","status":"publish","type":"post","link":"https:\/\/www.webcodegeeks.com\/web-development\/ridiculously-fast-api-authentication-phoenix\/","title":{"rendered":"Ridiculously Fast API Authentication with Phoenix"},"content":{"rendered":"<p>With Phoenix, productivity is a first-class citizen. <a href=\"https:\/\/www.webcodegeeks.com\/ruby\/introduction-apis-phoenix\/\">Last time<\/a>, we started an API and looked at how Phoenix promises similar if not more productivity than Rails. We scaffolded out a resource and talked about key concepts.<\/p>\n<p>Generators and scaffolds are a great way to see how things are done or to get an initial understanding. However, when building real-world applications, generators are rarely used outside of migrations. Today we\u2019ll look at how we can build on other people\u2019s work (dependency management), and then we\u2019ll create a simple token-based auth from scratch.<\/p>\n<p>If you played with the API we generated <a href=\"https:\/\/www.webcodegeeks.com\/ruby\/introduction-apis-phoenix\/\">last time<\/a>, you\u2019ll notice that it doesn\u2019t work from a browser. This is because modern browsers look for a CORS access control to help prevent cross site scripting.<\/p>\n<p><a href=\"http:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2016\/03\/No-CORS-access-control.png\" rel=\"attachment wp-att-11823\"><img decoding=\"async\" class=\"aligncenter size-large wp-image-11823\" src=\"http:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2016\/03\/No-CORS-access-control-1024x206.png\" alt=\"No-CORS-access-control\" width=\"620\" height=\"125\" srcset=\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2016\/03\/No-CORS-access-control-1024x206.png 1024w, https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2016\/03\/No-CORS-access-control-300x60.png 300w, https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2016\/03\/No-CORS-access-control-768x154.png 768w, https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2016\/03\/No-CORS-access-control.png 1402w\" sizes=\"(max-width: 620px) 100vw, 620px\" \/><\/a><\/p>\n<p>Much of Rails\u2019 popularity comes from its community. There are thousands of libraries for developers, and they\u2019re super simple to install thanks to RubyGems and Bundler. But there are a lot of concepts and gotchas. For one, gems are installed globally. Because of this, we need Gemsets and version managers to help.<\/p>\n<p>Once again, Elixir has taken the hard-earned lessons from Ruby and made things a little better. Elixir uses Hex as a package manager. As with all things in Elixir, Hex uses Mix (just like Phoenix). You add dependencies to the mix.exs file. Then tell Mix to get the files.<\/p>\n<p>Let\u2019s install <code>CORSPlug<\/code> to handle CORS for the API. First, add the dependency to the mix.exs file:<\/p>\n<pre class=\"brush:php\"># mix.exs\r\ndef deps do\r\n  # ...\r\n  {:cors_plug, \"~&gt; 1.1\"},\r\n  #...\r\nend<\/pre>\n<p>Then run the Mix command <code>deps.get<\/code> to install it:<\/p>\n<pre class=\"brush:php\">$ mix deps.get<\/pre>\n<p>Notice that it was installed locally to the <code>deps<\/code> folder. Installing dependencies locally prevents problems associated with global dependencies. It wouldn\u2019t be hard to add CORS to Phoenix yourself, but <code>CORSPlug<\/code> is very easy to use.<\/p>\n<p>Add the following to <code>lib\/todo_api\/endpoint.ex<\/code> before the router plug:<\/p>\n<pre class=\"brush:php\">defmodule TodoApi.Endpoint do\r\n  use Phoenix.Enpoint, otp_app: :your_app\r\n  # ...\r\n\r\n  plug CORSPlug\r\n\r\n  # add before this line\r\n  plug TodoApi.Router\r\nend<\/pre>\n<p>That\u2019s it. By default, <code>CORSPlug<\/code> allows all requests. Restart your server (<code>mix phoenix.server<\/code>), and now everything\u2019s working with the browser.<\/p>\n<h2>Creating Users<\/h2>\n<p>Our simple token-based auth system is going to require a user to authenticate. Rails has a helper called <code>has_secure_password<\/code> that is added to a model. This gives the model industrial strength encryption via bcrypt.<\/p>\n<p>Phoenix doesn\u2019t have a helper method like <code>has_secure_password<\/code>, but I think encryption is just as easy. We will use a Hex package named <code>comeonin<\/code>. This package uses bcrypt and will do the heavy lifting for us.<\/p>\n<p>Add <code>comeonin<\/code> to the applications list and the dependencies:<\/p>\n<pre class=\"brush:php\"># mix.exs\r\n\r\n# ...\r\n\r\ndef application do\r\n  [mod: {TodoApi, []},\r\n   applications: [:phoenix, :cowboy, :logger, :gettext,\r\n                  :phoenix_ecto, :postgrex, :comeonin]]\r\nend\r\n\r\n# ...\r\n\r\ndefp deps do\r\n  [{:phoenix, \"~&gt; 1.1.2\"},\r\n   {:phoenix_ecto, \"~&gt; 2.0\"},\r\n   {:postgrex, \"&gt;= 0.0.0\"},\r\n   {:gettext, \"~&gt; 0.9\"},\r\n   {:comeonin, \"~&gt; 2.0\"},\r\n   {:cowboy, \"~&gt; 1.0\"}]\r\nend\r\n\r\n# ...<\/pre>\n<p>Use Mix to download the dependencies:<\/p>\n<pre class=\"brush:php\">$ mix deps.get<\/pre>\n<p>The algorithm for bcrypt is purposefully slow. This extra slow hashing helps to prevent brute force attacks. If each attempt at guessing a password takes a fraction of a second, then millions of guesses take an eternity. A fraction of a second is imperceivable to humans who make one attempt at a time to hash a password.<\/p>\n<p>However, if that human is a developer and running a test suite, the tests can quickly take an eternity too. Luckily, <code>comeonin<\/code> allows us to speed up our tests by turning down the encryption. Add the following to the <code>config\/test.exs<\/code> so the tests stay fast:<\/p>\n<pre class=\"brush:php\"># config\/test.exs\r\nconfig :comeonin, :bcrypt_log_rounds, 4\r\nconfig :comeonin, :pbkdf2_rounds, 1<\/pre>\n<p>Now the system needs a user. Since Phoenix is a mature framework, it gives us migrations, just like Rails. Migrations are a programatic way to make changes to the database in a way that is reversible.<\/p>\n<p>Use mix to generate a migration:<\/p>\n<pre class=\"brush:php\">mix ecto.gen.migration create_user<\/pre>\n<p>Email and password are all that\u2019s needed for this simple API authentication system. But we don\u2019t want to store the password in the database \u2014 instead we want to store the hash that <code>comeonin<\/code> will generate for us. So add <code>email<\/code> and <code>password_hash<\/code> to the migration:<\/p>\n<pre class=\"brush:php\"># priv\/repo\/migrations\/20160120025135_create_user.exs\r\n\r\ndefmodule TodoApi.Repo.Migrations.CreateUser do\r\n  use Ecto.Migration\r\n\r\n  def change do\r\n    create table(:users) do\r\n      add :email, :string, null: false\r\n      add :password_hash, :string\r\n\r\n      timestamps\r\n    end\r\n\r\n    create unique_index(:users, [:email])\r\n  end\r\nend<\/pre>\n<p>Migrate the database to create the new table:<\/p>\n<pre class=\"brush:php\">$ mix ecto.migrate<\/pre>\n<p>In <a href=\"https:\/\/www.webcodegeeks.com\/ruby\/introduction-apis-phoenix\/\">the last blog post<\/a>, we looked at tests and talked about <code>changesets<\/code>. Here we\u2019ll create two <code>changesets<\/code>. One is for updating a user, or when the password is NOT present. The other is for registering or creating a user; this is necessary because in this scenario we need a password.<\/p>\n<p>We will start creating our functions by writing tests that exercise our current understanding of the system.<\/p>\n<pre class=\"brush:php\"># test\/models\/user_test.exs\r\ndefmodule TodoApi.UserTest do\r\n  use TodoApi.ModelCase\r\n\r\n  alias TodoApi.User\r\n\r\n  @valid_attrs %{email: \"bar@baz.com\", password: \"s3cr3t\"}\r\n\r\n  test \"changeset with valid attributes\" do\r\n    changeset = User.changeset(%User{}, @valid_attrs)\r\n    assert changeset.valid?\r\n  end\r\n\r\n  test \"changeset, email too short \" do\r\n    changeset = User.changeset(\r\n      %User{}, Map.put(@valid_attrs, :email, \"\")\r\n    )\r\n    refute changeset.valid?\r\n  end\r\n\r\n  test \"changeset, email invalid format\" do\r\n    changeset = User.changeset(\r\n      %User{}, Map.put(@valid_attrs, :email, \"foo.com\")\r\n    )\r\n    refute changeset.valid?\r\n  end\r\n\r\n  test \"registration_changeset, password too short\" do\r\n    changeset = User.registration_changeset(%User{}, @valid_attrs)\r\n    assert changeset.changes.password_hash\r\n    assert changeset.valid?\r\n  end\r\n\r\n  test \"registration_changeset, password too short\" do\r\n    changeset = User.registration_changeset(\r\n      %User{}, Map.put(@valid_attrs, :password, \"12345\")\r\n    )\r\n    refute changeset.valid?\r\n  end\r\nend<\/pre>\n<p>And now we create a model that meets our test assertions:<\/p>\n<pre class=\"brush:php\"># web\/models\/user.ex\r\ndefmodule TodoApi.User do\r\n  use TodoApi.Web, :model\r\n\r\n  schema \"users\" do\r\n    field :email, :string\r\n    field :password_hash, :string\r\n    field :password, :string, virtual: true\r\n\r\n    timestamps\r\n  end\r\n\r\n  def changeset(model, params \\\\ :empty) do\r\n    model\r\n    |&gt; cast(params, ~w(email), [])\r\n    |&gt; validate_length(:email, min: 1, max: 255)\r\n    |&gt; validate_format(:email, ~r\/@\/)\r\n  end\r\n\r\n  def registration_changeset(model, params \\\\ :empty) do\r\n    model\r\n    |&gt; changeset(params)\r\n    |&gt; cast(params, ~w(password), [])\r\n    |&gt; validate_length(:password, min: 6)\r\n    |&gt; put_password_hash\r\n  end\r\n\r\n  defp put_password_hash(changeset) do\r\n    case changeset do\r\n      %Ecto.Changeset{valid?: true, changes: %{password: pass}} -&gt;\r\n        put_change(changeset, :password_hash, Comeonin.Bcrypt.hashpwsalt(pass))\r\n      _ -&gt;\r\n        changeset\r\n    end\r\n  end\r\nend<\/pre>\n<p>The user model is straight forward but has three things to note:<\/p>\n<ol>\n<li>There is a virtual attribute <code>password<\/code>. This allows a password to be passed in, since we do not store the password in the database, only the encrypted hash.<\/li>\n<li>The <code>registration_changeset<\/code> function calls the other <code>changeset<\/code> function. This removes duplicated validations from the code. Since that function returns a <code>changeset<\/code>, you can just use it in your pipeline.<\/li>\n<li>The <code>put_password_hash<\/code> function pattern matches to see if the <code>changeset<\/code> is valid. If it is valid, it encrypts the password and adds it. In the case where the change is not valid, it doesn\u2019t calculate (or store) a password.<\/li>\n<\/ol>\n<p>Now that we have a user model, we need an endpoint in the API to create a user. Once again, we start with a test:<\/p>\n<pre class=\"brush:php\"># test\/controllers\/user_controller_test.exs\r\ndefmodule TodoApi.UserControllerTest do\r\n  use TodoApi.ConnCase\r\n\r\n  alias TodoApi.User\r\n  @valid_attrs %{email: \"foo@bar.com\", password: \"s3cr3t\"}\r\n  @invalid_attrs %{}\r\n\r\n  setup %{conn: conn} do\r\n    {:ok, conn: put_req_header(conn, \"accept\", \"application\/json\")}\r\n  end\r\n\r\n  test \"creates and renders resource when data is valid\", %{conn: conn} do\r\n    conn = post conn, user_path(conn, :create), user: @valid_attrs\r\n    body = json_response(conn, 201)\r\n    assert body[\"data\"][\"id\"]\r\n    assert body[\"data\"][\"email\"]\r\n    refute body[\"data\"][\"password\"]\r\n    assert Repo.get_by(User, email: \"foo@bar.com\")\r\n  end\r\n\r\n  test \"does not create resource and renders errors when data is invalid\", %{conn: conn} do\r\n    conn = post conn, user_path(conn, :create), user: @invalid_attrs\r\n    assert json_response(conn, 422)[\"errors\"] != %{}\r\n  end\r\n\r\nend<\/pre>\n<p>Notice we didn\u2019t write a test for the route first. The helper function <code>user_path\/2<\/code> in the above test would have been generated by the router. So when the test suite runs, it is expected to be red; however the reason is because there <code>user_path\/2<\/code> is not defined. Time to add a route:<\/p>\n<pre class=\"brush:php\"># web\/router.ex\r\nscope \"\/api\", TodoApi do\r\n  pipe_through :api\r\n\r\n  resources \"\/todos\", TodoController, except: [:new, :edit]\r\n  resources \"\/users\", UserController, only: [:create]\r\nend<\/pre>\n<p>Now our test suite requires us to implement a controller to pass:<\/p>\n<pre class=\"brush:php\">defmodule TodoApi.UserController do\r\n  use TodoApi.Web, :controller\r\n\r\n  alias TodoApi.User\r\n\r\n  plug :scrub_params, \"user\" when action in [:create]\r\n\r\n  def create(conn, %{\"user\" =&gt; user_params}) do\r\n    changeset = User.registration_changeset(%User{}, user_params)\r\n\r\n    case Repo.insert(changeset) do\r\n      {:ok, user} -&gt;\r\n        conn\r\n        |&gt; put_status(:created)\r\n        |&gt; render(\"show.json\", user: user)\r\n      {:error, changeset} -&gt;\r\n        conn\r\n        |&gt; put_status(:unprocessable_entity)\r\n        |&gt; render(TodoApi.ChangesetView, \"error.json\", changeset: changeset)\r\n    end\r\n  end\r\nend<\/pre>\n<p>This controller calls the <code>registration_changset\/2<\/code> function on the user model. The code attempts to insert the <code>changeset<\/code> in the database and follows the similar boiler plate logic we saw in <code>TodoController.create\/2<\/code>.<\/p>\n<p>Like we learned in <a href=\"https:\/\/www.webcodegeeks.com\/ruby\/introduction-apis-phoenix\/\">the last blog post<\/a>, controllers render views of the same name by default. There is no magic, so you could just as easily render another view or put view logic in your controller.<\/p>\n<p>For now, let\u2019s stick with the established patterns and create the conventional <code>UserView<\/code>.<\/p>\n<pre class=\"brush:php\">defmodule TodoApi.UserView do\r\n  use TodoApi.Web, :view\r\n\r\n  def render(\"show.json\", %{user: user}) do\r\n    %{data: render_one(user, TodoApi.UserView, \"user.json\")}\r\n  end\r\n\r\n  def render(\"user.json\", %{user: user}) do\r\n    %{id: user.id,\r\n      email: user.email}\r\n  end\r\nend<\/pre>\n<p>And that\u2019s it. Our API has a way to create a <code>User<\/code>. The only thing that is important to note is that the password was not returned in the JSON response. There was also a test case that covered that: <code>refute body[\"data\"][\"password\"]<\/code>.<\/p>\n<h2>Creating Sessions<\/h2>\n<p>When it comes to creating a user session, it would be easy to add a <code>token<\/code> to the user model. I, however, don\u2019t like this approach. If a user logs in from two devices, say a tablet and desktop, the only way to log out is to reset the token. This means if a user logs out from the tablet, they are also logged out on the desktop.<\/p>\n<p>Instead we will create a session table in the database. Each device will have its own token. If we need to log out of one device, we simply delete the session record in the database. There are other benefits as well. In the future, we could store information about sessions, like the type of device, or the ip of the request.<\/p>\n<p>Generate a <code>Session<\/code> model so you get the migration, the basic scaffold for <code>changesets<\/code>, and tests.<\/p>\n<pre class=\"brush:php\">$ mix phoenix.gen.model Session sessions user_id:references:users token<\/pre>\n<pre class=\"brush:php\"># priv\/repo\/migrations\/20160120043602_create_session.exs\r\ndefmodule TodoApi.Repo.Migrations.CreateSession do\r\n  use Ecto.Migration\r\n\r\n  def change do\r\n    create table(:sessions) do\r\n      add :token, :string\r\n      add :user_id, references(:users, on_delete: :nothing)\r\n\r\n      timestamps\r\n    end\r\n\r\n    create index(:sessions, [:user_id])\r\n    create index(:sessions, [:token])\r\n\r\n  end\r\nend<\/pre>\n<p>We now need to create a token for the session; let\u2019s add the <code>SecureRandom<\/code> before we write our test. <code>SecureRandom<\/code> is an almost direct port of Ruby\u2019s <code>SecureRandom<\/code> gem. I like it because it\u2019s easy.<\/p>\n<pre class=\"brush:php\">def deps do\r\n  # ...\r\n  {:secure_random, \"~&gt; 0.2\"},\r\n  #...\r\nend<\/pre>\n<p>Then run the Mix command to install it:<\/p>\n<pre class=\"brush:php\">$ mix deps.get<\/pre>\n<p>Now in our <code>SessionTest<\/code>, add a test case that asserts the token is generated for session creation:<\/p>\n<pre class=\"brush:php\">defmodule TodoApi.SessionTest do\r\n  use TodoApi.ModelCase\r\n\r\n  alias TodoApi.Session\r\n\r\n  @valid_attrs %{user_id: \"12345\"}\r\n  @invalid_attrs %{}\r\n\r\n  test \"changeset with valid attributes\" do\r\n    changeset = Session.changeset(%Session{}, @valid_attrs)\r\n    assert changeset.valid?\r\n  end\r\n\r\n  test \"changeset with invalid attributes\" do\r\n    changeset = Session.changeset(%Session{}, @invalid_attrs)\r\n    refute changeset.valid?\r\n  end\r\n\r\n  test \"create_changeset with valid attributes\" do\r\n    changeset = Session.create_changeset(%Session{}, @valid_attrs)\r\n    assert changeset.changes.token\r\n    assert changeset.valid?\r\n  end\r\n\r\n  test \"create_changeset with invalid attributes\" do\r\n    changeset = Session.create_changeset(%Session{}, @invalid_attrs)\r\n    refute changeset.valid?\r\n  end\r\nend<\/pre>\n<p>Now it\u2019s time to make the <code>Session<\/code> model:<\/p>\n<pre class=\"brush:php\">defmodule TodoApi.Session do\r\n  use TodoApi.Web, :model\r\n\r\n  schema \"sessions\" do\r\n    field :token, :string\r\n    belongs_to :user, TodoApi.User\r\n\r\n    timestamps\r\n  end\r\n\r\n  @required_fields ~w(user_id)\r\n  @optional_fields ~w()\r\n\r\n  @doc \"\"\"\r\n  Creates a changeset based on the `model` and `params`.\r\n\r\n  If no params are provided, an invalid changeset is returned\r\n  with no validation performed.\r\n  \"\"\"\r\n  def changeset(model, params \\\\ :empty) do\r\n    model\r\n    |&gt; cast(params, @required_fields, @optional_fields)\r\n  end\r\n\r\n  def registration_changeset(model, params \\\\ :empty) do\r\n    model\r\n    |&gt; changeset(params)\r\n    |&gt; put_change(:token, SecureRandom.urlsafe_base64())\r\n  end\r\nend<\/pre>\n<p>We went ahead and made two <code>changesets<\/code> even though we don\u2019t currently have a way to update one. My personal opinion is that when you see a small pattern like this, it\u2019s an easy win to extract early.<\/p>\n<p>Now write a test for the <code>SessionController<\/code>:<\/p>\n<pre class=\"brush:php\"># test\/controllers\/session_controller_test.exs\r\ndefmodule TodoApi.SessionControllerTest do\r\n  use TodoApi.ConnCase\r\n\r\n  alias TodoApi.Session\r\n  alias TodoApi.User\r\n  @valid_attrs %{email: \"foo@bar.com\", password: \"s3cr3t\"}\r\n\r\n  setup %{conn: conn} do\r\n    changeset =  User.registration_changeset(%User{}, @valid_attrs)\r\n    Repo.insert changeset\r\n    {:ok, conn: put_req_header(conn, \"accept\", \"application\/json\")}\r\n  end\r\n\r\n  test \"creates and renders resource when data is valid\", %{conn: conn} do\r\n    conn = post conn, session_path(conn, :create), user: @valid_attrs\r\n    assert token = json_response(conn, 201)[\"data\"][\"token\"]\r\n    assert Repo.get_by(Session, token: token)\r\n  end\r\n\r\n  test \"does not create resource and renders errors when password is invalid\", %{conn: conn} do\r\n    conn = post conn, session_path(conn, :create), user: Map.put(@valid_attrs, :password, \"notright\")\r\n    assert json_response(conn, 401)[\"errors\"] != %{}\r\n  end\r\n\r\n  test \"does not create resource and renders errors when email is invalid\", %{conn: conn} do\r\n    conn = post conn, session_path(conn, :create), user: Map.put(@valid_attrs, :email, \"not@found.com\")\r\n    assert json_response(conn, 401)[\"errors\"] != %{}\r\n  end\r\n\r\nend<\/pre>\n<p>Time to do the TDD dance. Add the route:<\/p>\n<pre class=\"brush:php\"># web\/router.ex\r\nresources \"\/sessions\", SessionController, only: [:create]<\/pre>\n<p>And the controller:<\/p>\n<pre class=\"brush:php\"># web\/controllers\/session_controller.ex\r\ndefmodule TodoApi.SessionController do\r\n  use TodoApi.Web, :controller\r\n\r\n  import Comeonin.Bcrypt, only: [checkpw: 2, dummy_checkpw: 0]\r\n\r\n  alias TodoApi.User\r\n  alias TodoApi.Session\r\n\r\n  def create(conn, %{\"user\" =&gt; user_params}) do\r\n    user = Repo.get_by(User, email: user_params[\"email\"])\r\n    cond do\r\n      user &amp;&amp; checkpw(user_params[\"password\"], user.password_hash) -&gt;\r\n        session_changeset = Session.crate_changeset(%Session{}, %{user_id: user.id})\r\n        {:ok, session} = Repo.insert(session_changeset)\r\n        conn\r\n        |&gt; put_status(:created)\r\n        |&gt; render(\"show.json\", session: session)\r\n      user -&gt;\r\n        conn\r\n        |&gt; put_status(:unauthorized)\r\n        |&gt; render(\"error.json\", user_params)\r\n      true -&gt;\r\n        dummy_checkpw\r\n        conn\r\n        |&gt; put_status(:unauthorized)\r\n        |&gt; render(\"error.json\", user_params)\r\n    end\r\n  end\r\nend<\/pre>\n<p>This is the trickiest controller so far. There are three possible outcomes:<\/p>\n<ol>\n<li>If the user is found and the password is correct, we insert a session into the database and return the token.<\/li>\n<li>If the user is found but the password is incorrect, an error is rendered.<\/li>\n<li>If the user is NOT found, then <code>dummy_checkpw<\/code> simulates a password check on a user as one was found, and returns an error. This is an important security method and strengthens the application\u2019s defense against timing attacks.<\/li>\n<\/ol>\n<p>Finally create the view:<\/p>\n<pre class=\"brush:php\"># web\/views\/session_view.ex\r\ndefmodule TodoApi.SessionView do\r\n  use TodoApi.Web, :view\r\n\r\n  def render(\"show.json\", %{session: session}) do\r\n    %{data: render_one(session, TodoApi.SessionView, \"session.json\")}\r\n  end\r\n\r\n  def render(\"session.json\", %{session: session}) do\r\n    %{token: session.token}\r\n  end\r\n\r\n  def render(\"error.json\", _anything) do\r\n    %{errors: \"failed to authenticate\"}\r\n  end\r\nend<\/pre>\n<p>So far, we have seen how productive we can be with Phoenix. In this blog post, we created authentication from scratch and even set up a token system that allows multiple sessions. We also test drove our API. To top that off, we did it in a very small amount of code.<\/p>\n<p>In my next blog post, we\u2019ll refactor the <code>TodosController<\/code> to validate the session tokens.<\/p>\n<div class=\"attribution\">\n<table>\n<tbody>\n<tr>\n<td><span class=\"reference\">Reference: <\/span><\/td>\n<td><a href=\"http:\/\/blog.codeship.com\/ridiculously-fast-api-authentication-with-phoenix\/\">Ridiculously Fast API Authentication with Phoenix<\/a> from our <a href=\"http:\/\/www.webcodegeeks.com\/join-us\/wcg\/\">WCG partner<\/a> Florian Motlik at the <a href=\"http:\/\/blog.codeship.com\/\">Codeship Blog<\/a> blog.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>With Phoenix, productivity is a first-class citizen. Last time, we started an API and looked at how Phoenix promises similar if not more productivity than Rails. We scaffolded out a resource and talked about key concepts. Generators and scaffolds are a great way to see how things are done or to get an initial understanding. &hellip;<\/p>\n","protected":false},"author":122,"featured_media":11828,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[8],"tags":[336],"class_list":["post-11819","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-web-development","tag-phoenix"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.5 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Ridiculously Fast API Authentication with Phoenix - Web Code Geeks - 2026<\/title>\n<meta name=\"description\" content=\"With Phoenix, productivity is a first-class citizen. Last time, we started an API and looked at how Phoenix promises similar if not more productivity than\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.webcodegeeks.com\/web-development\/ridiculously-fast-api-authentication-phoenix\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Ridiculously Fast API Authentication with Phoenix - Web Code Geeks - 2026\" \/>\n<meta property=\"og:description\" content=\"With Phoenix, productivity is a first-class citizen. Last time, we started an API and looked at how Phoenix promises similar if not more productivity than\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.webcodegeeks.com\/web-development\/ridiculously-fast-api-authentication-phoenix\/\" \/>\n<meta property=\"og:site_name\" content=\"Web Code Geeks\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/webcodegeeks\" \/>\n<meta property=\"article:published_time\" content=\"2016-04-08T09:11:24+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2017-04-12T09:36:42+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2016\/03\/phoenix-logo.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"150\" \/>\n\t<meta property=\"og:image:height\" content=\"150\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Micah Woods\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@mwoods79\" \/>\n<meta name=\"twitter:site\" content=\"@webcodegeeks\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Micah Woods\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"14 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.webcodegeeks.com\/web-development\/ridiculously-fast-api-authentication-phoenix\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/web-development\/ridiculously-fast-api-authentication-phoenix\/\"},\"author\":{\"name\":\"Micah Woods\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/4d78279b52f045f83cb6431d9435fb32\"},\"headline\":\"Ridiculously Fast API Authentication with Phoenix\",\"datePublished\":\"2016-04-08T09:11:24+00:00\",\"dateModified\":\"2017-04-12T09:36:42+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/web-development\/ridiculously-fast-api-authentication-phoenix\/\"},\"wordCount\":1435,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#organization\"},\"image\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/web-development\/ridiculously-fast-api-authentication-phoenix\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2016\/03\/phoenix-logo.jpg\",\"keywords\":[\"Phoenix\"],\"articleSection\":[\"Web Dev\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/www.webcodegeeks.com\/web-development\/ridiculously-fast-api-authentication-phoenix\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.webcodegeeks.com\/web-development\/ridiculously-fast-api-authentication-phoenix\/\",\"url\":\"https:\/\/www.webcodegeeks.com\/web-development\/ridiculously-fast-api-authentication-phoenix\/\",\"name\":\"Ridiculously Fast API Authentication with Phoenix - Web Code Geeks - 2026\",\"isPartOf\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/web-development\/ridiculously-fast-api-authentication-phoenix\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/web-development\/ridiculously-fast-api-authentication-phoenix\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2016\/03\/phoenix-logo.jpg\",\"datePublished\":\"2016-04-08T09:11:24+00:00\",\"dateModified\":\"2017-04-12T09:36:42+00:00\",\"description\":\"With Phoenix, productivity is a first-class citizen. Last time, we started an API and looked at how Phoenix promises similar if not more productivity than\",\"breadcrumb\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/web-development\/ridiculously-fast-api-authentication-phoenix\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.webcodegeeks.com\/web-development\/ridiculously-fast-api-authentication-phoenix\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.webcodegeeks.com\/web-development\/ridiculously-fast-api-authentication-phoenix\/#primaryimage\",\"url\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2016\/03\/phoenix-logo.jpg\",\"contentUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2016\/03\/phoenix-logo.jpg\",\"width\":150,\"height\":150},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.webcodegeeks.com\/web-development\/ridiculously-fast-api-authentication-phoenix\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.webcodegeeks.com\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Web Dev\",\"item\":\"https:\/\/www.webcodegeeks.com\/category\/web-development\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Ridiculously Fast API Authentication with Phoenix\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#website\",\"url\":\"https:\/\/www.webcodegeeks.com\/\",\"name\":\"Web Code Geeks\",\"description\":\"Web Developers Resource Center\",\"publisher\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.webcodegeeks.com\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#organization\",\"name\":\"Exelixis Media P.C.\",\"url\":\"https:\/\/www.webcodegeeks.com\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png\",\"contentUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png\",\"width\":864,\"height\":246,\"caption\":\"Exelixis Media P.C.\"},\"image\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/webcodegeeks\",\"https:\/\/x.com\/webcodegeeks\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/4d78279b52f045f83cb6431d9435fb32\",\"name\":\"Micah Woods\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/11195f1e0db3445d0ddbb346494dae8a5eae0229a91f5ed5eaae50f77eed23c7?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/11195f1e0db3445d0ddbb346494dae8a5eae0229a91f5ed5eaae50f77eed23c7?s=96&d=mm&r=g\",\"caption\":\"Micah Woods\"},\"description\":\"Micah discovered his passion for Ruby and web development in college. Pair programming at Hashrocket allows him to focus on testing and quality while delivering value at a blazing fast rate.\",\"sameAs\":[\"https:\/\/x.com\/mwoods79\"],\"url\":\"https:\/\/www.webcodegeeks.com\/author\/micah-woods\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Ridiculously Fast API Authentication with Phoenix - Web Code Geeks - 2026","description":"With Phoenix, productivity is a first-class citizen. Last time, we started an API and looked at how Phoenix promises similar if not more productivity than","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.webcodegeeks.com\/web-development\/ridiculously-fast-api-authentication-phoenix\/","og_locale":"en_US","og_type":"article","og_title":"Ridiculously Fast API Authentication with Phoenix - Web Code Geeks - 2026","og_description":"With Phoenix, productivity is a first-class citizen. Last time, we started an API and looked at how Phoenix promises similar if not more productivity than","og_url":"https:\/\/www.webcodegeeks.com\/web-development\/ridiculously-fast-api-authentication-phoenix\/","og_site_name":"Web Code Geeks","article_publisher":"https:\/\/www.facebook.com\/webcodegeeks","article_published_time":"2016-04-08T09:11:24+00:00","article_modified_time":"2017-04-12T09:36:42+00:00","og_image":[{"width":150,"height":150,"url":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2016\/03\/phoenix-logo.jpg","type":"image\/jpeg"}],"author":"Micah Woods","twitter_card":"summary_large_image","twitter_creator":"@mwoods79","twitter_site":"@webcodegeeks","twitter_misc":{"Written by":"Micah Woods","Est. reading time":"14 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.webcodegeeks.com\/web-development\/ridiculously-fast-api-authentication-phoenix\/#article","isPartOf":{"@id":"https:\/\/www.webcodegeeks.com\/web-development\/ridiculously-fast-api-authentication-phoenix\/"},"author":{"name":"Micah Woods","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/4d78279b52f045f83cb6431d9435fb32"},"headline":"Ridiculously Fast API Authentication with Phoenix","datePublished":"2016-04-08T09:11:24+00:00","dateModified":"2017-04-12T09:36:42+00:00","mainEntityOfPage":{"@id":"https:\/\/www.webcodegeeks.com\/web-development\/ridiculously-fast-api-authentication-phoenix\/"},"wordCount":1435,"commentCount":0,"publisher":{"@id":"https:\/\/www.webcodegeeks.com\/#organization"},"image":{"@id":"https:\/\/www.webcodegeeks.com\/web-development\/ridiculously-fast-api-authentication-phoenix\/#primaryimage"},"thumbnailUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2016\/03\/phoenix-logo.jpg","keywords":["Phoenix"],"articleSection":["Web Dev"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.webcodegeeks.com\/web-development\/ridiculously-fast-api-authentication-phoenix\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.webcodegeeks.com\/web-development\/ridiculously-fast-api-authentication-phoenix\/","url":"https:\/\/www.webcodegeeks.com\/web-development\/ridiculously-fast-api-authentication-phoenix\/","name":"Ridiculously Fast API Authentication with Phoenix - Web Code Geeks - 2026","isPartOf":{"@id":"https:\/\/www.webcodegeeks.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.webcodegeeks.com\/web-development\/ridiculously-fast-api-authentication-phoenix\/#primaryimage"},"image":{"@id":"https:\/\/www.webcodegeeks.com\/web-development\/ridiculously-fast-api-authentication-phoenix\/#primaryimage"},"thumbnailUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2016\/03\/phoenix-logo.jpg","datePublished":"2016-04-08T09:11:24+00:00","dateModified":"2017-04-12T09:36:42+00:00","description":"With Phoenix, productivity is a first-class citizen. Last time, we started an API and looked at how Phoenix promises similar if not more productivity than","breadcrumb":{"@id":"https:\/\/www.webcodegeeks.com\/web-development\/ridiculously-fast-api-authentication-phoenix\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.webcodegeeks.com\/web-development\/ridiculously-fast-api-authentication-phoenix\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.webcodegeeks.com\/web-development\/ridiculously-fast-api-authentication-phoenix\/#primaryimage","url":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2016\/03\/phoenix-logo.jpg","contentUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2016\/03\/phoenix-logo.jpg","width":150,"height":150},{"@type":"BreadcrumbList","@id":"https:\/\/www.webcodegeeks.com\/web-development\/ridiculously-fast-api-authentication-phoenix\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.webcodegeeks.com\/"},{"@type":"ListItem","position":2,"name":"Web Dev","item":"https:\/\/www.webcodegeeks.com\/category\/web-development\/"},{"@type":"ListItem","position":3,"name":"Ridiculously Fast API Authentication with Phoenix"}]},{"@type":"WebSite","@id":"https:\/\/www.webcodegeeks.com\/#website","url":"https:\/\/www.webcodegeeks.com\/","name":"Web Code Geeks","description":"Web Developers Resource Center","publisher":{"@id":"https:\/\/www.webcodegeeks.com\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.webcodegeeks.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.webcodegeeks.com\/#organization","name":"Exelixis Media P.C.","url":"https:\/\/www.webcodegeeks.com\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/","url":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png","contentUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png","width":864,"height":246,"caption":"Exelixis Media P.C."},"image":{"@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/webcodegeeks","https:\/\/x.com\/webcodegeeks"]},{"@type":"Person","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/4d78279b52f045f83cb6431d9435fb32","name":"Micah Woods","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/11195f1e0db3445d0ddbb346494dae8a5eae0229a91f5ed5eaae50f77eed23c7?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/11195f1e0db3445d0ddbb346494dae8a5eae0229a91f5ed5eaae50f77eed23c7?s=96&d=mm&r=g","caption":"Micah Woods"},"description":"Micah discovered his passion for Ruby and web development in college. Pair programming at Hashrocket allows him to focus on testing and quality while delivering value at a blazing fast rate.","sameAs":["https:\/\/x.com\/mwoods79"],"url":"https:\/\/www.webcodegeeks.com\/author\/micah-woods\/"}]}},"_links":{"self":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/posts\/11819","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/users\/122"}],"replies":[{"embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/comments?post=11819"}],"version-history":[{"count":0,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/posts\/11819\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/media\/11828"}],"wp:attachment":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/media?parent=11819"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/categories?post=11819"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/tags?post=11819"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}