How to add Map in phoenix conn's params in Plug

hello, I need to add some Map in phoenix conn’s params in Plug

for example :

def init(_params) do

	end

	def call(%Plug.Conn{params: %{"ptoken" => ptoken}} = conn, params) do
    outputs = case Guardian.decode_and_verify(ptoken) do
           {:ok, _climes} ->
             # I need to add it in blow conn , map like : %{"last_ip" => "127.1.2.3"}
             assign(conn, :user_ip_checked, params)
           {:error, _ms} ->
             conn
         	  |> put_status(403)
         		|> json(%{error_code: "403", error_msg: "You don't have an access."})
         	  |> halt()
     end
     outputs
	end

how can I do this ?

2 Likes

Given that conn is a struct, I would update it like this…

conn = %{conn | assigns: Map.merge(conn.assigns, %{"last_ip" => "127.1.2.3"})}

BTW It really depends what You want to do with assigns, You could use Map.update, or Map.merge or anything You need depending on situation.

With Map.update…

args = %{"last_ip" => "127.1.2.3"}
conn = Map.update(conn, :assigns, args, &Map.merge(&1, args))
2 Likes

Thanks , I edited your code and now it works for me

code :

conn = %{conn | params: Map.merge(conn.params, %{"last_ip" => "#{climes["ip"]}"})}
2 Likes

If You have only one key, “last_ip”, You could use Map.put like this. Use Map.put if You don’t care what was the previous value. Use Map.update if You want to combine the previous value with the new one.

last_ip = to_string(climes["ip"])
conn = %{conn | params: Map.put(conn.params, "last_ip", last_ip)}

Only if You want to add multiple keys, You would use Map.merge

You choose depending on your requirements.

I prefer to_string(climes[“ip”]}) over “#{climes[“ip”]}” when then is no real interpolation needed, but that is personal preference.

If your solution is working, that is fine too :slight_smile:

1 Like

With that out of the way:

Why? What is forcing you to break with convention:

params - the request params, the result of merging the :body_params and :query_params with :path_params.

What is it that makes it impossible to use:

assign(conn, :last_ip, "127.1.2.3")

Is there a compelling reason that last_ip has to be a string key?

1 Like

Hello , thank you for this, I have 3 microservices, the Ms1 sends Jwt token to Ms2 and Ms2 send it to M3
The token has 2 things like user Ip and Os info which is string , and I need to receive them in all router’s fn in Ms3, then I take them when I valid the token

my code is :

defmodule TrangellApiGateway.Plug.CheckPrivateToken do
	import Plug.Conn
	import Phoenix.Controller
  alias TrangellApiGateway.GuardianLib.Guardian

	def init(_params) do

	end

	def call(%Plug.Conn{params: %{"ptoken" => ptoken}} = conn, params) do
    outputs = case Guardian.decode_and_verify(ptoken) do
           {:ok, climes} ->
             conn = %{conn | params: Map.merge(conn.params, %{"last_ip" => "#{climes["ip"]}"})}
             assign(conn, :user_ip_checked, params)
           {:error, _ms} ->
             conn
         	  |> put_status(403)
         		|> json(%{error_code: "403", error_msg: "You don't have an access."})
         	  |> halt()
     end
     outputs
	end

	def call(conn, _params) do
	  conn
	  |> put_status(403)
		|> json(%{error_code: "403", error_msg: "You don't have an access."})
	  |> halt()
	end
end

you think why my way has a problem? I didn’t edit it , I had just updated Phoenix’s params before sending to normal action function.

Thank you too , yeh your help did that for me. and now I edited it in order to improve.

Yes but action functions receive 2 parameters:

  def index(conn, params) do
     ...
  end

Plug.Conn Connection fields:

  • assigns - shared user data as a map

So rather than polluting Plug.Conn.params with non-standard data, you should use Plug.Conn.assigns which is intended for “user data”, e.g.:

          {:ok, climes} ->
             conn
             |> assign(:last_ip,  "#{climes["ip"]}")
             |> assign(:user_ip_checked, params)

and then inside the action function retrieve it:


  def index(conn, params) do
     ...
     last_ip = conn.assigns[:last_ip]
     ...
  end
3 Likes

Hello @peerreynders , I can’t use this , because I send params which I want like this :

 def get_posts_category(conn, %{"seo_alias_link" => _seo_alias_link, "page_number" => _page_number, "page_size" => _page_size, "token" => _token, "ptoken" => _ptoken} = allreq) do

I send allreq by

  def get_posts_category_cms_sender(params) do
    post_sender_json_without_authorization(params, "post/category")
  end

  # POST
  defp post_sender_json_without_authorization(params, link) do
    body = Jason.encode!(params)
    HTTPoison.post(
      "#{@link}#{link}",
       body,
        [
          {"Content-Type", "application/json; charset=UTF-8"},
        ],
       [timeout: 50_000, recv_timeout: 50_000]
    )
  end

I send allreq when I put os info and ip in params plug , do you understand what I sad that why I send it like this ?

do you have a suggestion which I use instead of my way ?

for ex :

in real I send blow codes :
allreq + conn = %{conn | params: Map.merge(conn.params, %{"last_ip" => "#{climes["ip"]}"})}

The idea is not to subvert the intent behind Plug.Conn.params - it’s not a general data “dumping ground” - that is Plug.Conn.assigns job. So the strategy would be to put the information in Plug.Conn.assigns first and then combine it in the last possible moment. So start with

          {:ok, climes} ->
             conn =
               conn
               |> assign(:all_req,  %{"last_ip", "#{climes["ip"]}"})
               |> assign(:user_ip_checked, params)

And then have a helper function that assembles everything

def make_all_req(conn, params) do
  conn.assigns
  |> Map.get(:all_req, %{})
  |> Map.merge(conn.params)
  |> Map.merge(params)
end

which you can use at the last possible moment, like:

def get_posts_category(conn, %{"seo_alias_link" => _seo_alias_link, "page_number" => _page_number, "page_size" => _page_size, "token" => _token, "ptoken" => _ptoken} = params) do
   all_req = make_all_req(conn, params)
   ...
end

You really shouldn’t be sending the entire contents of the Plug.Conn structure.

  • The actual content may change in the next version of Plug.Conn
  • The organization of the Plug.Conn structure could change in the next version
  • By using Plug.Conn verbatim you are unnecessarily coupling other parts of your system to it (and the version that you started using).

In the interest of decoupling:

  • Decide what minimal information is actually useful.
  • Structure that information in a way the makes sense to your system. Your information structure has different reasons to change than those that will force a change on Plug.Conn.
2 Likes