req
req copied to clipboard
Redirect with cookie handling
A lot of pages have this redirect flow:
- user opens example.com/
- auth system adds cookie for user data, redirects again to example.com/ if cookie is not set
- 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.
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.
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)