Translating between different versions of Guardian

I’ve been following along with an Absinthe tutorial written for Phoenix 1.2, while I’m currently using Phoenix 1.4. Now, the tutorial uses Guardian 0.14 for authentication, where Phoenix 1.4 requires Guardian 1.0. So here’s my issue, Guardian 1.0 does not have you define serializers. I’ve read through the github page on how to update from 0.14 to 1.0, but am at a complete loss for how to fit the pieces together. I’ve tried a couple different things, but none have been successful. Would any possibly know how to translate:

This 0.14 code:

defmodule MyApp.GuardianSerializer do
  @behaviour Guardian.Serializer

  alias MyApp.Repo
  alias MyApp.User

  def for_token(user = %User{}), do: { :ok, "User:#{user.id}" }
  def for_token(_), do: { :error, "Unknown resource type" }

  def from_token("User:" <> id), do: { :ok, Repo.get(User, id) }
  def from_token(_), do: { :error, "Unknown resource type" }
end

To this 1.0 code:

defmodule MyApp.Guardian do
  use Guardian, otp_app: :my_app

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

  def subject_for_token(_, _) do
    {:error, :reason_for_error}
  end

  def resource_from_claims(claims) do
    {:ok, find_me_a_resource(claims["sub"])}
  end
  def resource_from_claims(_claims) do
    {:error, :reason_for_error}
  end
end

Basically, I have no idea what to do with:

find_me_a_resource(claims["sub"])

Any help would be greatly appreciated!

1 Like

You just need to extract the user id from the claims[sub].

The subject_for_token/2 function defines what you choose to put in your claims[sub]. In, your case resource.id

Then you resource_from_claims/1 should look like this:

def resource_from_claims(claims) do
  {:ok, claims["sub"]}
end

If you want to stay as close as possible to the original code, your module looks like this:

defmodule MyApp.Guardian do
  use Guardian, otp_app: :my_app

  def subject_for_token(user = %User{}, _claims) do
    {:ok, "User:#{to_string(resource.id)}"}
  end

  def subject_for_token(_, _) do
    {:error, :reason_for_error}
  end

  def resource_from_claims(claims) do
    "User: " <> user_id = claims["sub"]
    {:ok, Repo.get(User, user_id} # But a `case` to check the query return is not a bad idea...
  end
  def resource_from_claims(_claims) do
    {:error, :reason_for_error}
  end
end

Also, be aware that afaik in 1.0 claims and resource are no more injected in controllers, allowing to have
my_controller_function(conn, params, claims, resource) definition.
You have to use
resource = MyApp.Guardian..Plug.current_resource(conn)

Hope this helps

3 Likes

Excellent, this makes sense, thank you for the help! I had to make a slight modification (changed resource.id to user.id and included alias MyApp.Repo and alias MyApp.User).

It seems that I’m running into another issue at this point. I’m receiving the error this clause cannot match because a previous clause at line 15 always matches when I try to make the ecto migration. It’s referring to the first def resource_from_claims. I’m guessing this is because I have 2 functions with the same name, but I don’t understand why I don’t see this for the def subject_for_token functions if that’s the case. Again, thank you for the help :slight_smile:

You have this error because first function will match all the cases. Anything given to resource_from_claims will be bound to claims without exception.
Then the second function is useless.
The error result should be in the first one though, as I suggested in comment, like this for example:

def resource_from_claims(claims) do
    "User: " <> user_id = claims["sub"]
    case Repo.get(User, user_id) do
      %User{} = user -> 
        {:ok, user}
      _ ->
        {:error, :user_not_found}
    end
  end
1 Like