kjwvanijk

kjwvanijk

Bet wishes to everyone, especially to the ash core team members for creating such an amazing framework.

I have a question regarding ash_authentication. I added ash_authentication to my application and so far it is working splendid. The issue I have that is after I sign in the user does not get redirected to the page they were trying to go to. I did some debugging and I noticed that the return_to value in the session of the Authentication Controller is always nil thus the application defaults to the “/” route.

So the question is how can I set this variable so the user gets returned to the page they were trying to log in to.

Kind regards,

Showing Posts 1 to 10

zachdaniel

zachdaniel

Creator of Ash

I believe this is something that you’ll want to set yourself when you redirect the user to a route to log in. @jimsynz may be able to add some more context. Essentially somewhere in your app, either in a plug or in an on_mount hook (or both) is a thing that will redirect users to the log in page if they aren’t logged in. In that redirect, you’d want to set the return_to parameter to the url they were trying to access.

kjwvanijk

kjwvanijk OP

Thanks for the pointer, I will need to see a bit how I will approach this.

kjwvanijk

kjwvanijk OP

I added the following plug to my application.

For anyone willing to use this, a few pointers. The log statements are very useful for debugging and understanding whats going on in your app but they will slow down your app because they happen on every request. So remove them for serious use.

The @invalid_return_to is added so the return_to variable does not get overwritten during your authentication process. The exact values depend on the authentication system you are using. So you might need to change them to work with your app.

defmodule MyAppWeb.ReturnToPlug do
  import Plug.Conn

  @invalid_return_to ["auth", "sign-in", "sign-out"]

  def init(default), do: default

  def call(conn, _default) do
    IO.puts("""
    Verb: #{inspect(conn.method)}
    Host: #{inspect(conn.query_string)}
    Headers: #{inspect(conn.request_path)}
    session: #{inspect(get_session(conn))}
    """)

    conn.request_path
    |> is_invalid_return_to()
    |> if do
      conn
    else
      put_session(conn, :return_to, add_query_parameter(conn.request_path, conn.query_string))
    end
  end

  defp is_invalid_return_to(path) do
    @invalid_return_to
    |> Enum.map(fn invalid -> String.contains?(path, invalid) end)
    |> Enum.any?()
  end

  defp add_query_parameter(path, query) do
    if query == "" do
      path
    else
      "#{path}?#{query}"
    end
  end
end

And in the router add:

    plug MyAppWeb.ReturnToPlug
zachdaniel

zachdaniel

Creator of Ash

This is neat :slight_smile: Maybe we should add something like this to ash_authentication at some point.

kjwvanijk

kjwvanijk OP

You are welcome to use it, ;). I did not do a lot of testing yet so there might be a few bugs hiding.

I did a bit more testing and I noticed that when i signed out it keeps returning me to the login screen. This is because the auth_controller of AshAuthenticationPhoenix also makes use of the return_to value. In my case I just want it to return me to my homepage so with a small edit to the controller this was easily solved.

    return_to = g̵e̵t̵_̵s̵e̵s̵s̵i̵o̵n̵(̵c̵o̵n̵n̵,̵ ̵:̵r̵e̵t̵u̵r̵n̵_̵t̵o̵)̵ ̵|̵|̵ ~p"/"

Another solution might be to prevent setting the return_to variable when we have a logged in user.

camstuart

camstuart

That is very nice indeed, and helped me. thank you @kjwvanijk :pray:

ketupia

ketupia

It doesn’t look like a RetunToPlug has been added to AshAuthentication.Phoenix. Here’s my version. Thank you @kjwvanijk for your version and pointing me in the right direction.

defmodule YourAppWeb.Plugs.ReturnToPlug do
  @moduledoc """
  Plug to capture the return_to query parameter and store it in the session.
  This allows for proper redirection after successful authentication while
  preventing redirect loops to authentication-related pages.

  ## Options

    * `:paths` - A list of paths where the plug should capture the return_to parameter
      default: ["/sign-in"]
    * `:param_name` - The name of the query parameter to capture
      default: "return_to"
    * `:session_key` - The session key where the return path will be stored
      default: :return_to
    * `:blocked_redirect_paths` - A list of paths that should be blocked as return destinations.  This is a starts_with? comparison.
      default: ["/auth", "/password-reset", "/reset", "/register", "/sign-in", "/sign-out"]

  ## Examples

  Using default options:

      # In your router.ex
      pipeline :browser do
        # ...other plugs
        plug YourAppWeb.Plugs.ReturnToPlug
      end

  With custom paths:

      # Capture return_to on multiple paths
      plug YourAppWeb.Plugs.ReturnToPlug, paths: ["/sign-in", "/login", "/register"]

  Fully customized configuration:

      # Custom parameter name, session key, and blocked paths
      plug YourAppWeb.Plugs.ReturnToPlug,
        paths: ["/sign-in", "/login"],
        param_name: "redirect_to",
        session_key: :redirect_after_login,
        blocked_redirect_paths: ["/auth", "/password-reset", "/reset", "/register", "/sign-in", "/sign-out"]
  """
  import Plug.Conn

  @default_options [
    paths: ["/sign-in"],
    param_name: "return_to",
    session_key: :return_to,
    blocked_redirect_paths: [
      "/auth",
      "/password-reset",
      "/reset",
      "/register",
      "/sign-in",
      "/sign-out"
    ]
  ]

  def init(opts) do
    Keyword.merge(@default_options, opts)
  end

  def call(conn, opts) do
    conn = fetch_query_params(conn)

    # Check if current path is in the configured paths
    if matching_path?(conn, opts[:paths]) && has_return_to_param?(conn, opts[:param_name]) do
      # Extract the return_to parameter
      return_to = get_return_to_param(conn, opts[:param_name])

      # Only store it if it's not pointing to a blocked path
      if blocked_return_path?(return_to, opts[:blocked_redirect_paths]) do
        # If blocked, we could either keep the conn unchanged or clear any existing return_to
        # Here we choose to clear it to be extra safe
        delete_session(conn, opts[:session_key])
      else
        put_session(conn, opts[:session_key], return_to)
      end
    else
      conn
    end
  end

  # Checks if the current path matches any of the configured paths
  defp matching_path?(conn, paths) do
    Enum.member?(paths, conn.request_path)
  end

  # Checks if the request has the configured query parameter
  defp has_return_to_param?(conn, param_name) do
    conn.query_params[param_name] != nil
  end

  # Gets the configured parameter value from the query parameters
  defp get_return_to_param(conn, param_name) do
    conn.query_params[param_name]
  end

  # Checks if the return path starts with any of the blocked prefixes
  defp blocked_return_path?(return_path, blocked_redirect_paths) do
    path_to_check = URI.parse(return_path).path

    Enum.any?(blocked_redirect_paths, fn prefix ->
      String.starts_with?(path_to_check, prefix)
    end)
  end
end

ketupia

ketupia

@zachdaniel Would you like me to create an issue against AshAuthenticationPhoenix for either of these versions?

Seems like a great candidate for both a standalone igniter task for devs to use to augment their installs.

zachdaniel

zachdaniel

Creator of Ash

Yes please. We would likely just include something like this in the default installer.

— All posts loaded —

Where Next? Top

Trending in Questions Top

katta
I having some trouble figuring out if I have set myself too strict of standards for my production server. Currently I can handle 75% of r...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
achenet
Hello, I’m trying to build a basic Phoenix web-app, and I’d like to use Tailwind. However, when I launch mix phx.server, I get an error...
New
kpanic
Hi everyone, I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding. I sta...
New
asweet-confluent
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New
Cxx-mlr
I’m working on a small exercise involving update_in/3, and I came up with this solution: data = %{ name: "Periodic Table", category:...
New
ChrisAmelia
I’ve got trouble wrapping my head around the order in which functions are called in this snippet (from Phoenix’s authentication): toke...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews