Passing Phoenix.Token to and from browser - authentication

I’m trying to use Openmaize for user authentication, and having trouble getting phoenix pass a token when a user logs in. It appears that no token is assigned and passed to the client, and therefore Phoenix.Token.verify fails.
IO.inspect(socket) in UserSocket.connect returns this.
Phoenix.Socket{assigns: %{}, channel: nil, channel_pid: nil,
endpoint: SeatSaver.Endpoint, handler: SeatSaver.UserSocket, id: nil,
joined: false, pubsub_server: SeatSaver.PubSub, ref: nil,
serializer: Phoenix.Transports.WebSocketSerializer, topic: nil,
transport: Phoenix.Transports.WebSocket, transport_name: :websocket,
transport_pid: #PID<0.2098.0>}
I defined set_current_user(user, conn) function in authenticate.ex controller that looks like
defp set_current_user(user, conn) do
token = Phoenix.Token.sign(conn, "user socket", user.id)
conn
|> assign(:current_user, user)
|> assign(:user_token, token)
In the app.html.eex, the following has been added.
<script> window.userToken = “<%= assigns[:user_token] %>” </script>
<script src = “<%= static_path(#conn, “/js/app.js”) %>”></script>
in the app.js,
let socket = new Socket(”/socket”, {
params: {token: window.userToken},
…
})
and finally, user_socket.ex has
def connect(%{"token" => token}, socket) do
case Phoenix.Token.verify(socket, "user socket",
token, max_age: #max_age) do
{:ok, user_id} ->
IO.inspect(user_id)
{:ok, assign(socket, :user_id, user_id)}
{:error, _reason} ->
:error # this errors out because token is nil
end
end

First you need to add secret_key_base in config/config.exs.
secret_key_base: xxxxx

Related

Authentication with csrf_token in Phoenix / Elixir

I am doing a project where the front-end is managed with Vue.js and the back with elixir / phoenix framework for my api.
I need to manage the authentication of my users using csrf-token and JWT.
I am using guardian library for the jwt part (https://hexdocs.pm/guardian/readme.html)
and I am currently blocking on the csrf_token part.
I want to generate a csrf-token that I will put in the jwt when sending the login form from vue.js and that will be returned when the user is connected.
I read the documentation but I don't see how to implement the csrf-token if the html page is not generated by phoenix. (https://hexdocs.pm/plug/Plug.CSRFProtection.html)
So how can I manage the generation of a csrf-token with phoenix in my vue.js html page?
Here is the route I use for the connection :
scope "/api", AuthTutorialPhoenixWeb do
pipe_through(:api)
post("/users/sign_in", SessionController, :new)
end
And this is my controller :
defmodule AuthTutorialPhoenixWeb.SessionController do
use AuthTutorialPhoenixWeb, :controller
alias AuthTutorialPhoenix.Accounts
alias AuthTutorialPhoenix.Authentication.Guardian
action_fallback(AuthTutorialPhoenixWeb.FallbackController)
# new session
def new(conn, %{"email" => email, "password" => password}) do
case Accounts.authenticate_user(email, password) do
{:ok, user} ->
# Lifetime Token of 15 minutes
{:ok, access_token, _claims} =
Guardian.encode_and_sign(user, %{}, token_type: "access", ttl: {15, :minute})
{:ok, refresh_token, _claims} =
Guardian.encode_and_sign(user, %{}, token_type: "refresh", ttl: {7, :day})
conn
|> put_resp_cookie("ruid", refresh_token)
|> put_status(:created)
|> render("token.json", access_token: access_token, user: user)
{:error, :unauthorized} ->
body = Jason.encode!(%{error: "unauthorized"})
conn
|> send_resp(401, body)
end
end
end
This is a little late to the party, but you can generate the CSRF token on demand with Plug.CSRFProtection.get_csrf_token() and send it to the front end

how to retry connect ampq in Elixir

I'm trying to connect to a RabbitMQ instance using the ampq package on Elixir, but at times the RabbitMQ instance won't be available at the time that the Elixir server is running. I was wondering how I might be able to implement a simple retry mechanism. There's one strategy here but that seems more involved than I feel necessary especially since there's a mention of it on the README about more information being found on the official docs. I unfortunately couldn't find anything.
Edit: This will crash the application on start and exit.
My code for the module is as follows:
Server.Gen.Rabbit (child)
defmodule Server.Gen.Rabbit do
use GenServer
use AMQP
defmodule State do
#type t :: %{
id: String.t(),
chan: map()
}
defstruct id: "", chan: nil
end
def start_link(%{id: id}) do
GenServer.start_link(
__MODULE__,
%State{
id: id
},
name: :"#{id}:rabbit"
)
end
def init(opts) do
host = "amqp://guest:guest#localhost"
case Connection.open(host) do
{:ok, conn} ->
{:ok, chan} = Channel.open(conn)
setup_queue(opts.id, chan)
:ok = Basic.qos(chan, prefetch_count: 1)
queue_to_consume = #online_receive_queue <> opts.id
IO.puts("queue_to_consume_online: " <> queue_to_consume)
{:ok, _consumer_tag} = Basic.consume(chan, queue_to_consume, nil, no_ack: true)
{:ok, %State{chan: chan, id: opts.id}}
{:error, _} ->
IO.puts("[Rabbit] error on connecting to server: #{host}")
{:backoff, 5_000}
end
end
Server (parent)
defmodule Server do
use Application
def start(_type, _args) do
import Supervisor.Spec, warn: false
children = [
{
GenRegistry,
worker_module: Server.Gen.Rabbit
},
Plug.Cowboy.child_spec(
scheme: :http,
plug: Server.Router,
options: [
port: String.to_integer(System.get_env("PORT") || "3000"),
dispatch: dispatch(),
protocol_options: [idle_timeout: :infinity]
]
)
]
opts = [strategy: :one_for_one, name: Server.Supervisor]
Supervisor.start_link(children, opts)
end
end

Issue with Guardian - EnsureAuthenticated not working

I'm attempting to get Guardian auth work for my application. But I'm completely stuck and can't find any support for the problem I'm having.
As far as I know I've setup Guardian exactly how the documentation shows how, but when I test authentication in the browser it fails on EnsureAuthenticated plug that Guardian provides.
Here is what I'm working with:
CONFIG:
All values are filled correctly in the app.
config :statcasters, MyApp.Guardian,
allowed_algos: ["HS512"],
verify_module: Guardian.JWT,
issuer: "my_app",
ttl: {30, :days},
allowed_drift: 2000,
verify_issuer: true,
secret_key: "my_secret_key"
AUTHENTICATED CONTROLLER:
defmodule Statcasters.LeagueController do
use StatcastersWeb, :controller
alias Statcasters.{League, Repo}
plug Guardian.Plug.EnsureAuthenticated
def create(conn, %{"league" => league_params}) do
changeset = League.changeset(%League{}, league_params)
case Repo.insert(changeset) do
{:ok, league} ->
conn
|> put_status(:created)
|> render("league.json", league: league)
{:error, changeset} ->
conn
|> put_status(:unprocessable_entity)
|> render(Statcasters.ChangesetView, "error.json", changeset: changeset)
end
end
end
In this controller is where it fails. When it goes to the EnsureAuthenticated plug it halts right there. but I have a valid JWT in the headers at this point.
Here our my params:
Parameters: %{"headers" => %{"Authorization" => "Bearer eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJTdGF0Y2FzdGVycyIsImV4cCI6MTUyNzUzMDA1OSwiaWF0IjoxNTI0OTM4MDU5LCJMiOiJTdGF0Y2FzdGVycyIsImp0aSI6IjJhNDg3MWQ4LTkwZGEtNDNlYS1hMGJlLWVjNjgwNjIzOTBkOCIsIm5iZiI6MTUyNDkzODA1OCwic3ViIjoiMSIsInR5cCI6InJlZnJlc2gifQ.EKeaHoQiW9tmtsabPIjj6069zD6Vcex9w3xfkXP5MIyiogWh400S6wMzaAsTQd20I5ai_y9jJTtgLzqYfbGTaQ"}
I've verified that the JWT is valid here.
REQUEST:
axios.post('/api/v1/leagues', {
league: {
name: this.$refs.league_name.value,
player_limit: this.$refs.player_limit.value,
},
headers: {
Authorization: "Bearer jwt(correct jwt)"
}
}).then(response => {
}).catch(error => {
})
Again, the problem is that my auth is failing in the Plug.EnsureAuthenticated hook. But I can't understand why because I seem to be setting everything up correctly and the JWT is in the auth header.
You're sending the header as a POST parameter, not an HTTP header. You need to put the headers in the third argument for axios.post:
axios.post('/api/v1/leagues', {
league: {
name: this.$refs.league_name.value,
player_limit: this.$refs.player_limit.value,
}
}, {
headers: {
Authorization: "Bearer jwt(correct jwt)"
}
})

Ajax POST request not hitting Elixir router path

I have this redux-observable epic which does a POST ajax request using RxJS.ajax.post and I don't think it is hitting my Elixir router properly as nothing is happening on my elixir backend. I am doing get requests to get categories correctly and in the same manner so I am hitting other paths in my Elixir router correctly. I am expecting the issue to be with my backend Elixir code not my frontend. I might need to change my path in router.ex.
When I press a button on the frontend, this object is what gets sent to the elixir backend (it dispatches this action with a product as the payload and hits the redux-observable epic below):
onPress = {() => {
props.uploadProduct({
name: props.name,
brand: props.brand,
description: props.description,
image: props.image
})
The epic:
import { ajax } from 'rxjs/observable/dom/ajax'
import { Observable } from 'rxjs'
export const uploadProductEpic = action$ =>
action$.ofType(UPLOAD_PRODUCT)
.mergeMap(action => {
ajax.post('http://localhost:4000/products/', action.payload)
})
.map(response => uploadProductFulfilled(response))
.catch(error => Observable.of(
uploadProductRejected(error))
)
the elixir router:
defmodule Api.Router do
use Plug.Router
if Mix.env == :dev do
use Plug.Debugger
end
plug :match
plug :dispatch
get "/categories/" do
Api.Repo.getCategories(conn)
end
post "/products/:product" do
IO.puts inspect conn
Api.Repo.insertProduct(conn, product)
end
end
IO.puts inspect conn doesn't log anything. So My Elixir router path post "/products/:product" do is not being hit by my POST request. What am I doing wrong?
This is the elixir function in repo.ex that I HOPE will insert the product into my database:
def insertProduct(conn, product) do
product = %Api.Product{name: product.name, brand: product.brand, description: product.description, image: product.image, rating: 0, numberOfVotes: 0}
changeset = Api.Product.changeset(product)
errors = changeset.errors
valid = changeset.valid?
case insert(changeset) do
{:ok, product} ->
conn
|> put_resp_content_type("application/json")
|> send_resp(200, Poison.encode!(%{
successs: "success"
}))
{:error, changeset} ->
conn
|> put_resp_content_type("application/json")
|> send_resp(500, Poison.encode!(%{
failure: "Errors"
}))
end
end
I am a frontend developer just trying to get into Elixir so any guidance and patience is appreciated. Thanks.
Your data is sent in the body of the request, not in the URL, so the route should be:
post "/products"
You'll also need to plug in a JSON parser after plug :match and before plug :dispatch, as described in the Parameter Parsing section in the documentation of Plug.Router:
plug :match
plug Plug.Parsers, parsers: [:json],
pass: ["application/json"],
json_decoder: Poison
plug :dispatch
The JSON data will now be present in conn.body_params, which you can send to your function:
post "/products" do
Api.Repo.insertProduct(conn, conn.body_params)
end
And finally, the keys in the JSON would be strings, so you need to use the bracket syntax to access them instead of dots:
product = %Api.Product{name: product["name"], brand: product["brand"], description: product["description"], image: product["image"], rating: 0, numberOfVotes: 0}
I'm not sure how you've defined Api.Product.changeset, but the default Phoenix convention defines a 2 arity function which calls cast and extracts the data from a map itself. If you're doing the same, you can do this instead:
changeset = Api.Product.changeset(%Api.Product{}, product)

How to find out the current logged in user using Guardian in Elixir/Phoenix

I'd like to find out the current user from the server side. The user_controller module shows how current_user can be returned in response to the client request. But I'm having trouble finding out how to do it from another module on the server.
Below is what I tried but failed with the following error. Basically I copied over the Guardian.Plug code block from the user_controller.ex.
What's the correct way to do it?
(CompileError) web/GraphQLSession.ex:9: undefined function put_status/2
(stdlib) lists.erl:1337: :lists.foreach/2
(stdlib) erl_eval.erl:669: :erl_eval.do_apply/6
.
defmodule App.GraphQLSession do
use Guardian.Hooks
def root_eval(_conn) do
current_user =
case Guardian.Plug.current_resource(_conn) do
nil ->
_conn
|> put_status(:not_found)
|> render(App.V1.SessionView, "error.json", error: "User not found")
user ->
_conn
|> put_status(:ok)
|> render("show.json", user: user)
end
%{author: current_user}
end
end
<<<<< router.ex >>>>
defmodule App.Router do
use App.Web, :router
...
pipeline :api do
plug :accepts, ["json"]
plug Guardian.Plug.VerifyHeader
plug Guardian.Plug.LoadResource
end
scope "/graphql" do
pipe_through :api
forward "/", GraphQL.Plug, [schema: {App.PublicSchema, :schema}, root_value: {App.GraphQLSession, :root_eval} ]
end
<<<< user_controller.ex >> .... This shows how client can retrieve current_user via controller.
defmodule App.V1.UserController do
use App.Web, :controller
alias App.User
plug Guardian.Plug.EnsureAuthenticated, on_failure: { App.V1.SessionController, :unauthenticated_api }
plug :scrub_params, "user" when action in [:create, :update]
def current_user(conn, %{"jwt" => jwt}) do
case Guardian.Plug.current_resource(conn) do
nil ->
conn
|> put_status(:not_found)
|> render(App.V1.SessionView, "error.json", error: "User not found")
user ->
conn
|> put_status(:ok)
|> render("show.json", user: user)
end
UPDATE: After importing Plug.Conn and changing _conn to conn, per David Sulc's advice, the next error I get is as follows:
[error] #PID<0.1026.0> running App.Endpoint terminated
Server: localhost:4000 (http)
Request: POST /graphql
** (exit) an exception was raised:
** (Protocol.UndefinedError) protocol Enumerable not implemented for %Plug.Conn{adapter: {Plug.Adapters.Cowboy.Conn, :...}, assigns: %{error: "User not found", guardian_default_resource: nil, layout:
false}, before_send: [#Function<1.34093945/1 in Plug.Logger.call/2>, #Function<0.30221299/1 in Phoenix.LiveReloader.before_send_inject_reloader/1>], body_params: %{"query" => "mutation CreateMutation(
$input_0:CreateInput!){createQ(input:$input_0){clientMutationId,...F3}} fragment F0 on ...}
}}, cookies: %Plug.Conn.Unfetched{aspect: :cookies}, halted: false, host: "localhost", method: "POST", owner: #PID<0.1026.0>, params: %{"query" => "mutation ....}}}, path_info: [], peer: {{1
27, 0, 0, 1}, 50944}, port: 4000, private: %{App.Router => {[], %{GraphQL.Plug => []}}, :phoenix_endpoint => App.Endpoint, :phoenix_format => "json", :phoenix_pipelines => [:api], :phoenix_route => #Funct
ion<0.58758354/1 in App.Router.match_route/4>, :phoenix_router => App.Router, :phoenix_template => "error.json", :phoenix_view => App.V1.SessionView, :plug_session_fetch => #Function<0.29336444/1 in Plug.
Session.fetch_session/1>}, query_params: %{}, query_string: "", remote_ip: {127, 0, 0, 1}, req_cookies: %Plug.Conn.Unfetched{aspect: :cookies}, req_headers: [{"host", "localhost:4000"}, {"connection", "ke
ep-alive"}, {"content-length", "632"}, {"accept", "*/*"}, {"origin", "http://localhost:4000"}, {"user-agent", "}, {"content-type", "application/json"}, {"referer", "http://localhost:4000/"}, {"accept-encoding", "gzip, deflate"}, {"accept-language", "en-US,en;q=0.8,ko;q=0.6,zh-CN;q=0.4"
}, {"alexatoolbar-alx_ns_ph", "AlexaToolbar/alx-4.0"}], request_path: "/graphql", resp_body: nil, resp_cookies: %{}, resp_headers: [{"content-type", "application/json; charset=utf-8"}, {"cache-control", "
max-age=0, private, must-revalidate"}, {"x-request-id", "ikh03v5kqightov3npgl8bv0do5rv77d"}, {"access-control-allow-origin", "*"}, {"access-control-expose-headers", ""}, {"access-control-allow-credentials
", "true"}], scheme: :http, script_name: ["graphql"], secret_key_base: "x4K=====-00-----lksMUX", state: :sent, status: 404}
(elixir) lib/enum.ex:1: Enumerable.impl_for!/1
(elixir) lib/enum.ex:116: Enumerable.reduce/3
(elixir) lib/enum.ex:1477: Enum.reduce/3
(elixir) lib/enum.ex:1092: Enum.map/2
(rethinkdb) lib/rethinkdb/query/macros.ex:93: RethinkDB.Query.Macros.wrap/1
(rethinkdb) lib/rethinkdb/query/macros.ex:94: anonymous fn/1 in RethinkDB.Query.Macros.wrap/1
(elixir) lib/enum.ex:1092: anonymous fn/3 in Enum.map/2
(stdlib) lists.erl:1262: :lists.foldl/3
I'm learning Elixir myself, but hopefully this will help you along...
Elixir is complaining because the controller imports Plug modules for you (via use App.Web, :controller). This isn't your case in the module you defined, so you need to import it yourself (see line 2).
Also, note that the naming convention of preceding a variable with an underscore if for variables that are to be ignored (and therefore not used). Since your code uses the conn param, it shouldn't be matched as _conn.
defmodule App.GraphQLSession do
import Plug.Conn, only: [put_status: 2]
def root_eval(conn) do
current_user =
case Guardian.Plug.current_resource(conn) do
nil ->
conn
|> put_status(:not_found)
|> render(App.V1.SessionView, "error.json", error: "User not found")
user ->
conn
|> put_status(:ok)
|> render("show.json", user: user)
end
%{author: current_user}
end
end
To answer your updated question there are a few things that you should notice:
Your main error is that you're trying to implement the Enumerable protocol for something that doesn't support it. See the line: ** (Protocol.UndefinedError) protocol Enumerable not implemented. Check any code that is using the Enum.* module. Make sure anything you're passing into the function is enumerable.
Also, Guardian can't find the user you want to retrieve. In the stack trace you can see this: assigns: %{error: "User not found", guardian_default_resource: nil. Try using the api_sign_in function provided by Guardian. You can find more information here. Then you should be able to use Guardian.Plug.current_resource(conn)