• Phoenix: Build a full-fledged API in five minutes

I’ll show how you can build a full-fledged API in minutes. It’s super fast to prototype in Elixir, but it can take some time to figure out all the working parts. Lucky for us, we can use some hex libraries that’ll do most of the work for us.

I’ve the following assumptions about the app I want to build:

  • User registration and login pages
  • Backend for users to create applications, access tokens and revoke access
  • Scopes for access tokens
  • OAuth 2.0 capabilities for third party app access or personal access tokens
  • API versioning

This is how you achieve all this in just five minutes so you can focus on building your app instead.

Install Phoenix 1.3

mix archive.install https://github.com/phoenixframework/archives/raw/master/phx_new.ez

Create new Phoenix app

mix phx.new myapp
cd myapp

User authentication

Add coherence to mix.exs:

def deps do
  [
    {:phoenix, "~> 1.3.0"},
    {:coherence, "~> 0.5.0"},
    # ...
  ]
end

Install dependencies:

rm mix.lock
mix deps.get

We remove the lock file because there’s conflict issue between coherence and Phoenix 1.3

Run the install script:

mix coh.install --full-confirmable --model="Users.User users"

Modify lib/myapp_web/router.ex to look like this:

defmodule MyappWeb.Router do
  use MyappWeb, :router
  use Coherence.Router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  pipeline :api do
    plug :accepts, ["json"]
  end

  pipeline :public do
    plug Coherence.Authentication.Session
  end

  pipeline :protected do
    plug Coherence.Authentication.Session, protected: true
  end

  scope "/" do
    pipe_through [:browser, :public]
    coherence_routes()
  end

  scope "/" do
    pipe_through [:browser, :protected]
    coherence_routes :protected
  end

  scope "/", MyappWeb do
    pipe_through [:browser, :public]

    get "/", PageController, :index
  end

  # Other scopes may use custom stacks.
  # scope "/api", MyappWeb do
  #   pipe_through :api
  # end
end

Finally, update priv/repo/seeds.exs:

Myapp.Repo.delete_all Myapp.Users.User

Myapp.Users.User.changeset(%Myapp.Users.User{}, %{name: "Test User", email: "testuser@example.com", password: "secret", password_confirmation: "secret"})
|> Myapp.Repo.insert!
|> Coherence.ControllerHelpers.confirm!

Oauth 2.0

Add phoenix_oauth2_provider to mix.exs:

def deps do
  [
    {:phoenix, "~> 1.3.0"},
    {:phoenix_oauth2_provider, "~> 0.3.0"},
    # ...
  ]
end

Install dependencies:

mix deps.get

Run the install script:

mix phoenix_oauth2_provider.install --resource-owner=Myapp.Users.User

Modify lib/myapp_web/router.ex to look like this:

defmodule MyappWeb.Router do
  use MyappWeb, :router
  use Coherence.Router
  use PhoenixOauth2Provider.Router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  pipeline :api do
    plug :accepts, ["json"]
  end

  pipeline :public do
    plug Coherence.Authentication.Session
  end

  pipeline :protected do
    plug Coherence.Authentication.Session, protected: true
  end

  pipeline :api_auth do
    plug ExOauth2Provider.Plug.VerifyHeader, realm: "Bearer"
    plug ExOauth2Provider.Plug.EnsureAuthenticated
  end

  pipeline :oauth_public do
    plug :put_secure_browser_headers
  end

  scope "/" do
    pipe_through :oauth_public
    oauth_routes :public
  end

  scope "/" do
    pipe_through [:browser, :public]
    coherence_routes()
  end

  scope "/" do
    pipe_through [:browser, :protected]
    coherence_routes :protected
    oauth_routes :protected
  end

  scope "/", MyappWeb do
    pipe_through [:browser, :public]

    get "/", PageController, :index
  end

  # Other scopes may use custom stacks.
  # scope "/api", MyappWeb do
  #   pipe_through :api
  # end
end

Set up versioned API

Create account controller:

mix phx.gen.json Users User users --web API.V1 --no-schema

Add the following scope block somewhere in lib/myapp_web/router.ex:

defmodule MyappWeb.Router do
  # ...

  scope "/api/v1", MyappWeb.API.V1 do
    pipe_through :api_auth
    resources "/accounts", UserController
  end

  # ...
end

Remove all other actions in lib/myapp_web/controllers/api/v1/user_controller.ex and make it so there’s just this one index action:

defmodule MyappWeb.API.V1.UserController do
  use MyappWeb, :controller

  action_fallback MyappWeb.FallbackController

  def index(conn, _params) do
    users = [ExOauth2Provider.Plug.current_resource_owner(conn)]
    render(conn, "index.json", users: users)
  end
end

Migrate and set up seed data

mix ecto.setup

Note: In case you don’t have the default postgres user, you can create it like so:

createuser postgres --pwprompt -d

Use postgres as password.

That’s it!

Start the server:

mix phoenix.server
  • Visit http://0.0.0.0:4000/oauth/applications.
  • Login with testuser@example.com / secret.
  • Create a new application with urn:ietf:wg:oauth:2.0:oob as Redirect URI
  • Save the application, and take note of of the Application ID and Secret.
  • Click Authorize next to the Redirect URI, authorize yourself and copy the access grant shown.

Now we got everything to generate the access token. Replace CLIENT_ID, CLIENT_SECRET and AUTHORIZATION_CODE in the following curl command, and run it:

curl -X POST "http://0.0.0.0:4000/oauth/token?client_id=CLIENT_ID&client_secret=CLIENT_SECRET&grant_type=authorization_code&code=AUTHORIZATION_CODE&redirect_uri=urn:ietf:wg:oauth:2.0:oob"

You’ll receive an access token along with other details. Copy the access token, and use it for to retrieve a resource:

curl http://0.0.0.0:4000/api/v1/accounts/ \
   -H "Authorization: Bearer ACCESS_TOKEN"

You’ll see a response like this:

{"data":[{"id":1}]}

Source code: https://github.com/danschultzer/phoenix_ex_oauth2_provider_demo

The Author

Dan Schultzer is an active experienced entrepreneur, starting the Being Proactive groups, Dream Conception organization, among other things. You can find him at twitter

Like this post? More from Dan Schultzer

Comments? We would love to hear from you, write us at @dreamconception.