req icon indicating copy to clipboard operation
req copied to clipboard

Redirect with cookie handling

Open bitboxer opened this issue 1 year ago • 1 comments
trafficstars

A lot of pages have this redirect flow:

  1. user opens example.com/
  2. auth system adds cookie for user data, redirects again to example.com/ if cookie is not set
  3. browser loads example.com/ again

If the cookie coming from the redirect is not set and send to step 3, the auth system will again do the redirect. This will end up redirecting till the TooManyRedirectsError is fired.

An example of this behavior is the washington post (try with this: https://www.washingtonpost.com/business/2024/05/19/consumer-sentiment-vibes/)

The redirect system of req should have a minimal cookie handling in place for this.

bitboxer avatar May 19 '24 12:05 bitboxer

Thanks for the issue. Just letting you know I definitely want to solve it before Req v1.0 but the solution is most likely not straightforward.

wojtekmach avatar Jun 25 '24 08:06 wojtekmach

I am running into a related problem at the moment as I am trying to submit a login form of a Django application using req. I do not run into a TooManyRedirectsError but the root cause might be the very same.

First working code was something like this:

req =
  Req.new(base_url: "http://localhost:8010")
  |> ReqEasyHTML.attach()

response = Req.get!(req, url: "/admin/login/")
cookie = Req.Response.get_header(response, "set-cookie")
[csrfmiddlewaretoken] = response.body.nodes
  |> Floki.find("[name=csrfmiddlewaretoken]")
  |> Floki.attribute("value")

response = Req.post!(
  req,
  url: "/admin/login/",
  headers: %{ "cookie" => Enum.join(cookie, "; ") },
  form: [
    csrfmiddlewaretoken: csrfmiddlewaretoken,
    username: "myusername",
    password: "mypassword",
    next: "/admin/",
  ],
  redirect: false
)
cookie = Req.Response.get_header(response, "set-cookie")

response = Req.get(req, url: "/admin/", headers: %{ "cookie" => Enum.join(cookie, "; ") })

It works, but as soon as I remove the redirect: false (as the redirect plugin defaults to following redirects), the correct cookies are lost when Django redirects after successful login, and the get request shows the login form again.

To clean it up a little I came up with this plugin (don't know if it's there already but also wanted to get my feet wet with req 😄):

defmodule ReqCookieJar do
  def attach(%Req.Request{} = request) do
    request
    |> Req.Request.register_options([:cookie])
    |> Req.Request.append_request_steps(cookie: &set_cookie/1)
  end

  defp set_cookie(request) do
    set_cookie(request, request.options[:cookie])
  end

  defp set_cookie(request, [_|_] = cookie) do
    set_cookie(request, Enum.join(cookie, "; "))
  end

  defp set_cookie(request, cookie) when is_binary(cookie) do
    Req.Request.put_header(request, "cookie", cookie)
  end

  defp set_cookie(request, _other), do: request
end

So now it looks like this of course

req =
  Req.new(base_url: "http://localhost:8010")
  |> ReqEasyHTML.attach()
  |> ReqCookieJar.attach()

response = Req.get!(req, url: "/admin/login/")
cookie = Req.Response.get_header(response, "set-cookie")
[csrfmiddlewaretoken] = response.body.nodes
  |> Floki.find("[name=csrfmiddlewaretoken]")
  |> Floki.attribute("value")

response = Req.post!(
  req,
  url: "/admin/login/",
  cookie: cookie,
  form: [
    csrfmiddlewaretoken: csrfmiddlewaretoken,
    username: "myusername",
    password: "mypassword",
    next: "/admin/",
  ],
  redirect: false
)
cookie = Req.Response.get_header(response, "set-cookie")

response = Req.get(req, url: "/admin/", cookie: cookie)

But looking at what the redirect plugin does (specifically how it builds the redirect request) there seems to be no way to carry over specific information (such as the cookie header).

Couldn't the problem be solved by having a carry over mechanism/callback of some kind that receives the to be made request and can then decide what things to adjust in the redirect request? (e.g. adding a cookie value stored in priv given the schema/domain matches)

rmoorman avatar Nov 11 '24 02:11 rmoorman