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