Using EmberJS and Elixir together
Last year was the year of emberjs for me. I changed my ways from developing ruby on rails apps using server side presentation, to developing a web frontend, SPA, and connecting it to a server api.
While doing this, emberjs and ruby on rails were my preferred platform. I tested and used for a while sailsjs, in nodejs, because I was not happy with ruby concurrency problems ( more concurrency problems then actual speed ).
This year, decidedly will/is the year of elixir. I am currently in love with elixir and phoenix framework, just like I was with ruby on rails in the beginning.
This will be a series of posts, being this the part 1, in how to:
- create a new phoenix server api
- using jsonapi package for jsonapi responses
- add phoenix token auth, to enable authentication
- creating a user resource
- add cors
- access protected users resource
In part 2, I will talk about:
- create a new emberjs front end with ember-cli
- add authorization using ember-simple-auth
- create a login page and protected page
I will not go into how to install and configure elixir or nodejs and ember-cli, since there is already enough tutorials on the web on how to do that. My goal is elixir and emberjs integration.
So, assuming you have a working elixir environment, lets start.
Creating a new phoenix application
mix phoenix.new server_api --no-brunch
This will create a new application, called server_api. The option — no-brunch, is to not add brunch ( nodejs for managing assets ). Mix will ask to fetch and install dependencies, answer Y.
Adding jsonapi and phoenix token auth package to mix.exs dependencies
Add
{:jsonapi, "~> 0.0.2"},
{:plug_cors, "~> 0.7"},
{:phoenix_token_auth, github: "alexjp/phoenix_token_auth"}
to mix.exs. It should look like this:
defp deps do
[{:phoenix, “~> 0.14.0”},
{:phoenix_ecto, “~> 0.4”},
{:postgrex, “>= 0.0.0”},
{:phoenix_html, “~> 1.0”},
{:phoenix_live_reload, “~> 0.4”, only: :dev},
{:cowboy, “~> 1.0”},
{:jsonapi, “~> 0.0.2”},
{:plug_cors, “~> 0.7”},
{:phoenix_token_auth, github: “alexjp/phoenix_token_auth”}]
end
Then just do:
mix deps.get
It should fetch the dependencies.
We will be using a custom phoenix_token_auth from github, because it has some changes to work with ember-simple-auth. This will only be temporary and hopping to soon be able to do a proper pull request to the upstream package. One important change, is that this version uses a single authentication token auth, while upstream uses an array.
Assuming your config/dev.exs is properly configured ( configure database username and password ), we can continue to creating a user resource.
Creating the user resource
mix phoenix.gen.json User users username:string hashed_password:string hashed_confirmation_token:string confirmed_at:datetime hashed_password_reset_token:string unconfirmed_email:string authentication_token:string
This is similar to rails, emberjs, sailsjs generators for resources. Doing phoenix.gen.json, it will generate migration, model, controller, views, changeset and tests.
In the output, it should contain:
Add the resource to your api scope in web/router.ex:
resources "/users", UserController
and then update your repository by running migrations:
mix ecto.migrate
First command will create the users endpoint in the router.
Second command will perform the migration. Before the migration, its best if you do:
mix ecto. create
to create the database.
Editing web/router.ex, you should end up with something like this:
scope "/api", ServerApi do
pipe_through :api resources "/users", UserController
end
Configuring authentication
In this version of phoenix token auth, the router configuration will be slightly different:
require PhoenixTokenAuth........pipeline :authenticated do
plug PhoenixTokenAuth.Plug
endscope “/api”, ServerApi do
pipe_through :api
pipe_through :authenticated resources “/users”, UserController
endscope “/api” do
pipe_through :api PhoenixTokenAuth.sessions
PhoenixTokenAuth.register
PhoenixTokenAuth.mount
end
This will allow for a :autenticated pipeline, which all resources in it will need to be authenticated. The session (login/logout) routes, will not be authenticated. But you can change so that register and user management will, simply put them inside :authenticated pipeline.
You can check the routes with:
mix phoenix.routes
Next we will need to configure phoenix token auth. To do this, add in config/config.exs:
config :phoenix_token_auth,
user_model: ServerApi.User,
repo: ServerApi.Repo,
crypto_provider: Comeonin.Bcrypt,
token_validity_in_minutes: 7 * 24 * 60,
user_model_validator: {ServerApi.User, :user_validator}config :joken,
json_module: PhoenixTokenAuth.PoisonHelper,
algorithm: :HS256,
secret_key: "very secret test key"
This is configuring the model and repo to be used. Also includes support for validations in the model.
We also setup the joken configuration, the secret_key should be in dev/prod/test environments, I will keep it together for simplicity reasons.
In web/models/user.ex, add:
def user_validator(changeset = %{params: %{"password" => password}}) when password != nil and password != "" do
if String.length(password) < 6 do
changeset = Ecto.Changeset.add_error(changeset, :password, :too_short)
end
changeset
end
def user_validator(changeset), do: changeset
as the :user_validator configured above in config/config.exs
Configuring jsonapi
The controllers and views are configured to normal json responses. To enable jsonapi responses, we will have to change the user controller and view.
In the controller, the changes are simple:
Where there is this:
render(conn, "index.json", users: users)
change to
render conn, "index.json", %{data: users, params: _params}
and
render(conn, "show.json", user: user)
or
render conn, "show.json", user: user
change to
render conn, "show.json", %{data: user}
In the view,
def render("index.json", %{users: users}) do
%{data: render_many(users, "user.json")}
enddef render("show.json", %{user: user}) do
%{data: render_one(user, "user.json")}
enddef render("user.json", %{user: user}) do
%{id: user.id}
end
change to
use JSONAPI.PhoenixViewdef type, do: "user"def attributes(model) do
Map.take(model, [:name,
:username,
:email])
enddef relationships() do
%{
}
enddef url_func() do
&user_url/3
end
Adding cors
Plugcors will enable managing cors, needed later for emberjs.
To enable cors, its necessary to add:
plug PlugCors
to the lib/server_api/endpoint.ex, before plug :router. It should look like this:
......
plug PlugCors
plug :router, ServerApi.Router
In web/router.ex, replace:
pipeline :api do
plug :accepts, ["json"]
end
with
pipeline :api do
plug :accepts, ["json"]
plug PlugCors, [origins: ["*"]]
end
This will enable cors for all origins. Later, we should specify the server where emberjs is.
Taking it for a test drive
Bringing the server online with mix phoenix.server and using an app like chromium postman, you can test the url :
localhost:4000/api/users
. You should receive a :
{"error":"Not authorized"}
Sending:
{"user": {"username": "userexample", "email": "user@example.com", "password": "secret"}}
to the route:
localhost:4000/api/auth/users
should yield a response of:
{"status":"ok","confirmation_token": "BZ7FZf0MqZ4enqi0XMsUyp9NHaxYzzLdLJcyeaOTEzIJtYhFjgIb5zFDWSnZ8FsOqLmMshH0Dw1u1WUj5hYHzQ"}
Confirming the user, sending:
{"confirmation_token":"BZ7FZf0MqZ4enqi0XMsUyp9NHaxYzzLdLJcyeaOTEzIJtYhFjgIb5zFDWSnZ8FsOqLmMshH0Dw1u1WUj5hYHzQ"}
to the route:
localhost:4000/api/auth/users/1/confirm
should yield a response of:
{"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl9pZCI6IkZpbjUrMU85SncyV3hGV3pmYjB5UFZPZ3pySU5UclJpIiwiaWQiOjQsImV4cCI6MTQzNjYyMTU2NX0.gQsCpAT8PJNWPbE0HCJPzjStVP-AJCpTSAvspig4PpY"
}
Sending to route:
localhost:4000/api/auth/sessions
json:
{"username": "userexample", "password": "secret", "grant_type": "password"}
should yield:
{"user_id":1,"token_type":"bearer","access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl9pZCI6IkZpbjUrMU85SncyV3hGV3pmYjB5UFZPZ3pySU5UclJpIiwiaWQiOjQsImV4cCI6MTQzNjYyMTU2NX0.gQsCpAT8PJNWPbE0HCJPzjStVP-AJCpTSAvspig4PpY"}
Finally, testing it by querying users, authenticated:
Add this to the headers in postman:
Authorization: Bearer
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl9pZCI6IkZpbjUrMU85SncyV3hGV3pmYjB5UFZPZ3pySU5UclJpIiwiaWQiOjQsImV4cCI6MTQzNjYyMTU2NX0.gQsCpAT8PJNWPbE0HCJPzjStVP-AJCpTSAvspig4PpY
You should be able to see the list of users ( as without it, you should receive a not authenticated response ).
Wrapping up
With this base you should be able to use elixir as a server api with authorization for emberjs.
Ember-simple-auth, will send the login json like in the above example and it should work out of the box with ember data.
Next up, creating the emberjs app and connecting it to elixir server api.