• 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.4

mix archive.install hex phx_new 1.4.4

Create new Phoenix app

mix phx.new my_app
cd my_app

User authentication

Add pow to mix.exs:

def deps do
  [
    {:phoenix, "~> 1.4.4"},
    {:pow, "~> 1.0.7"},
    # ...
  ]
end

Install the dependencies:

mix deps.get

Run the install script:

mix pow.install

Update config/config.ex:

config :my_app, :pow,
  user: MyApp.Users.User,
  repo: MyApp.Repo

Update lib/my_app_web/endpoint.ex (adding the Pow.Plug.Session after the plug Plug.Session):

defmodule MyAppWeb.Endpoint do
  use Phoenix.Endpoint, otp_app: :my_app

  # ...

  plug Plug.Session,
    store: :cookie,
    key: "_my_app_key",
    signing_salt: "secret"

  plug Pow.Plug.Session, otp_app: :my_app

  # ...
end

Update lib/my_app_web/router.ex:

defmodule MyAppWeb.Router do
  use MyAppWeb, :router
  use Pow.Phoenix.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

  scope "/" do
    pipe_through :browser

    pow_routes()
  end

  scope "/", MyAppWeb do
    pipe_through :browser

    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.Users.User{}
|> MyApp.Users.User.changeset(%{
  email: "testuser@example.com",
  password: "secret1234",
  confirm_password: "secret1234"})
|> Myapp.Repo.insert!()

OAuth 2.0

Add phoenix_oauth2_provider to mix.exs:

def deps do
  [
    {:phoenix, "~> 1.4.4"},
    {:pow, "~> 1.0.7"},
    {:phoenix_oauth2_provider, "~> 0.5.1"},
    # ...
  ]
end

Install dependencies:

mix deps.get

Run the install script:

mix ex_oauth2_provider.install

Update config/config.ex:

config :my_app, ExOauth2Provider,
  repo: MyApp.Repo,
  resource_owner: MyApp.Users.User

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

defmodule MyAppWeb.Router do
  use MyAppWeb, :router
  use Pow.Phoenix.Router
  use PhoenixOauth2Provider.Router, otp_app: :my_app

  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 :protected do
    plug Pow.Plug.RequireAuthenticated,
      error_handler: Pow.Phoenix.PlugErrorHandler
  end

  scope "/" do
    pipe_through :browser

    pow_routes()
  end

  scope "/" do
    pipe_through :api

    oauth_api_routes()
  end

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

    oauth_routes()
  end

  scope "/", MyAppWeb do
    pipe_through :browser

    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 --no-context

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

defmodule MyAppWeb.Router do
  # ...

  pipeline :api_protected do
    plug ExOauth2Provider.Plug.VerifyHeader, otp_app: :my_app, realm: "Bearer"
    plug ExOauth2Provider.Plug.EnsureAuthenticated
  end

  # ...

  scope "/api/v1", MyAppWeb.API.V1 do
    pipe_through [:api, :api_protected]

    resources "/accounts", UserController
  end

  # ...
end

Remove all other actions in lib/my_app_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, otp_app: :my_app)]
    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://localhost:4000/oauth/applications.
  • Login with testuser@example.com / secret1234.
  • Create a new application with urn:ietf:wg:oauth:2.0:oob as Redirect URI
  • Save the application, and take note of of the 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://localhost: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://localhost: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.