guardian_phoenix icon indicating copy to clipboard operation
guardian_phoenix copied to clipboard

LiveView & Guardian

Open romaluca opened this issue 5 years ago • 11 comments

Hi, in LiveView i tried to use import Guardian.Phoenix.Socket ... current_resource(socket)

But doesn't work. How can i get the current user without get again from db? Thanks

romaluca avatar Feb 17 '20 12:02 romaluca

I didn't try with this library, but I was able to load the resource from the session when mounting a liveview with plain Guardian. Hope this helps!

defmodule MyAppWeb.Live.AuthHelper do
  @moduledoc "Helpers to assist with loading the user from the session into the socket"
  @claims %{"typ" => "access"}
  @token_key "guardian_default_token"

  def load_user(%{@token_key => token}) do
    case Guardian.decode_and_verify(MyApp.Accounts.Guardian, token, @claims) do
      {:ok, claims} ->
        MyApp.Accounts.Guardian.resource_from_claims(claims)
      _ ->
        {:error, :not_authorized}
    end
  end
  def load_user(_), do: {:error, :not_authorized}
end


# and in a Live view

  def mount(_params, session, socket) do
    {:ok, user} = MyApp.Live.AuthHelper.load_user(session)
    socket = assign(socket, :current_user, user)
    # ...
  end

For comparison for a connection w/ Plug:

# router.ex
  pipeline :load_auth do
    plug Guardian.Plug.Pipeline,
      module: MyApp.Accounts.Guardian,
      error_handler: MyApp.AccountErrorHandler

    plug Guardian.Plug.VerifySession, claims: %{"typ" => "access"}
    plug Guardian.Plug.VerifyHeader, claims: %{"typ" => "access"}
    plug Guardian.Plug.LoadResource, allow_blank: true
  end


# guardian.ex
defmodule MyApp.Accounts.Guardian do
  @moduledoc "Convert JWT to and from user"
  use Guardian, otp_app: :my_app
  alias MyApp.Accounts

  def subject_for_token(user, _claims) do
    {:ok, to_string(user.id)}
  end

  def resource_from_claims(%{"sub" => id}) do
    case Accounts.find_user(id) do
      nil -> {:error, :not_found}
      user -> {:ok, user}
    end
  end
end

dbernheisel avatar Mar 08 '20 22:03 dbernheisel

I am planning to use this in LiveView. Can you assist a bit more. I want to build a small system using LiveView (for running a simple system related to the current COVID pandemic). In another portion of the same project for rest API I am already using guardian for user authentication based on Bearer Token.

mssobhan avatar Apr 26 '20 20:04 mssobhan

Hi @mssobhan. Did you try the above suggested solution?

Hanspagh avatar May 11 '20 07:05 Hanspagh

Hi @mssobhan. Did you try the above suggested solution?

I will try it out and update you today. I missed this code sample (it was sandwiched). Btw, the login page url will be different, of will it be in the same url, with a signup / login fields, and then this code will be applied? Is it something like that.

mssobhan avatar May 11 '20 13:05 mssobhan

The login page will just put the token in the session or cookie or where tou have decided to put it, this should not affect the above

Hanspagh avatar May 11 '20 15:05 Hanspagh

for those looking for a quick solution

  • on liveview mount/3 the second parameter is a session. if user is authenticated using guardian, it includes a "guardian_default_token"
  • using Guardian.resource_from_token/1 will return {:ok, resource, claims}.
  • you can add a helper in your guardian implementation that would combine both steps into a resource_from_session if you want to make it shorter

happysalada avatar Jun 01 '20 10:06 happysalada

i created a fork of this project and add Guardian.Phoenix.SocketLive, i hope it help https://github.com/erikhu/guardian_phoenix , only you need to get is the token from the session and use it same as Guardian.Phoenix.Socket, i am trying to wonder some more wonderful idea to resolve it

erikhu avatar Jan 24 '21 06:01 erikhu

Hi @erikhu, what is the reason for not just using the Guardian.Phoenix.Socket module to do the auth, as I recall liveview just uses a normal socket to communicate over, correct me if I am wrong but I don't see a need for a special liveview socket module.

Hanspagh avatar Jan 24 '21 09:01 Hanspagh

@Hanspagh the type of liveview socket is different to the websocket, so if you use Guardian.Phoenix.Socket.authenticate(socket, MyApp.Guardian, token) in liveview it raise exception when try to assign the values, you could check it. anyway i would like to found out a better solution to it

erikhu avatar Jan 24 '21 16:01 erikhu

@Hanspagh Here's the specific error in case it helps anyone else. The issue is the guardian_phoenix is aliasing Phoenix.Socket while LiveViews use Phoenix.LiveView.Socket, so the pattern match for assign/3 fails.

I'm happy to attempt a PR if the authors think it's worth implementing.

** (exit) an exception was raised:
    ** (FunctionClauseError) no function clause matching in Phoenix.Socket.assign/3
        (phoenix 1.6.6) lib/phoenix/socket.ex:321: Phoenix.Socket.assign(#Phoenix.LiveView.Socket<assigns: %{__changed__: %{}, flash: %{}, live_action: :index}, endpoint: FzHttpWeb.Endpoint, id: "phx-Ftc0Q0t22biVJgCF", parent_pid: nil, root_pid: nil, router: FzHttpWeb.Router, transport_pid: nil, view: FzHttpWeb.DeviceLive.Unprivileged.Index, ...>, :guardian_default_token, "...")

jamilbk avatar Feb 26 '22 02:02 jamilbk

for those looking for a quick solution

  • on liveview mount/3 the second parameter is a session. if user is authenticated using guardian, it includes a "guardian_default_token"
  • using Guardian.resource_from_token/1 will return {:ok, resource, claims}.
  • you can add a helper in your guardian implementation that would combine both steps into a resource_from_session if you want to make it shorter

thank you so much!!! I got stuck for 2 hours till I found this solution...

sukidhar avatar Mar 03 '22 15:03 sukidhar