One-time link generation

Hello there,

I have a simple phoenix app and I need to add a change password (Forgot Password) feature. I want to generate a temporary one-time link to be sent to the user’s email. Here is my idea; I will generate a Base64-encoded string and add it at the end of the URL and save it in my DB for a certain amount of time. Whenever the user tries to use the URL in checks if it still in the DB or not, If so it will let the user continue to change his password else it will show a 404 NOT FOUND page.

Is this secure enough? and Is it a good solution generally? I appreciate any help, feedback or suggestions on how this could be done optimally.

Thank you in advance!

Save it with an expiry… it avoids having to clean up db all the time.

3 Likes

The new Phoenix auth generator from @josevalim has some similarities to that. You might consider using that generator or studying its code for best practices. The original design spec describes the general password reset flow.

I’ve only used an expiring token myself like @kokolegorille , but am curious about the tokens table method used in phx.gen.auth.

5 Likes

Great idea, is that available in Ecto through some option or I will have to do it by a trigger or something similar?

Thank you so much, I will study the code and use as a guide for best practices.

It’s not in Ecto, You just need to add an expiry datetime field, and use a query to check if it is still valid.

But as mentionned by @baldwindavid, this functionality is included in the new auth generator.

2 Likes

In these cases (ephemeral data which can be recreated) I use processes rather than the database. I just start a process with timeout as expiry and the unique code in the process register.

2 Likes

I recently implemented a version of this using LiveView that doesn’t require any db interaction.

The user goes to the LiveView page and enters their email. A pin is generated and sent to the email and stored in the LiveView state on the server side. When the pin is entered, it allows the user to change their password.

The only downside is they need to keep that live view window open, but it saves even having to deal with the DB.

4 Likes

I’m new to elixir and functional programming generally, so I’m kind of not grasping what you just said. I will appreciate if you could send me some resources to understand that. Thank you so much!

That is a great approach! :+1:t3: :+1:t3:

Elixir (and erlang and other beam languages) are concurrency first. Processes are very lightweight “micro-threads” each with its own state and garbage collection. They are the building blocks for most components in elixir/erlang. On top of bare processes you have the OTP libraries, which abstracts away the underlying details and lets you use best practices when writing concurrent code.

I think going through the getting started guide on the https://elixir-lang.org/getting-started should give you a good idea, especially https://elixir-lang.org/getting-started/processes.html and the Mix and OTP sections.

In terms of one-time link generation, my idea was to start one process per code, each which will timeout and die (and clean up after itself) after a predetermined period of time.

An example which uses elixir Registry (https://hexdocs.pm/elixir/Registry.html) and GenServer (https://hexdocs.pm/elixir/GenServer.html) to provide generation and verification of one time code could look like this:



defmodule OnetimeServer do
  use GenServer

  @default_timeout 10_000

  @doc """
  For testing, start this in your supervision tree
  """
  def start() do
    Registry.start_link(name: Registry.OnetimeServer, keys: :unique)
  end

  @doc """
     Generate code and start a GenServer and register in the registry
  """
  def make_code(timeout \\ @default_timeout) do
    code = :crypto.strong_rand_bytes(32) |> Base.encode64()
    GenServer.start(__MODULE__, [code, timeout], name: {:via, Registry, {Registry.OnetimeServer, code}})
    code
  end

  @doc """
  Verify the code. Looks up the code in the process register and calls it
  for verifcation
  """
  def verify_code(code) do
    case Registry.lookup(Registry.OnetimeServer, code) do
      [{pid, _}] ->
        GenServer.call(pid, {:verify_code, code})
      [] ->
        {:error, :invalid_code}
    end
  end

  #
  # Gen Server calls backs. THe init returns the timeout
  #
  def init([code, timeout]), do: {:ok, code, timeout}

  def handle_call({:verify_code, code}, _, code), do: {:stop, :normal, :ok, :done}
  def handle_call({:verify_code, _other}, _, _code), do: {:stop, :normal, :error, :done}

  def handle_info(:timeout, _code), do: {:stop, :normal, :done}

end

And then you can use this code:

ex(1)> OnetimeServer.start()
{:ok, #PID<0.199.0>}
iex(2)> c = OnetimeServer.make_code()
"W4x46tZfJenEjwqQcJDYngm+Vp80k5XP+VJoMIOrNZI="
iex(3)> OnetimeServer.verify_code(c)
:ok
iex(4)> OnetimeServer.verify_code(c)
{:error, :invalid_code}
iex(5)> c = OnetimeServer.make_code(1_000) # Shorter time out
"vXUj1AbOfWNpfEAoMIHmepXiXbN9PxZsaI7I68rnaXc="
iex(6)> OnetimeServer.verify_code(c)
{:error, :invalid_code}

There are many other ways to implement these in elixir, ets tables, mnesia, and other types of process registers, and even without process register just using process primitives.

Here is another example without the process registry which relies on sending a serialized pid to the client which is de-serialized on return. The one thing to be careful about here is accepting data from non-trusted clients. I slapped on an hmac on the process to make sure it is not tampered with but might give you an idea what is possible

defmodule OneTimeGenerator do

  # Should be fetched from secure configuration
  @hmackey :crypto.strong_rand_bytes(32)

  def create_code() do
    pid = Process.spawn(&OneTimeGenerator.code_checker/0, [])
    encode_pid(pid)
  end

  def verify_code(code) do
    case decode_pid(code) do
      {:ok, pid} ->
        verify_code(Process.alive?(pid), pid)
      {:error, :invalid_code} ->
        {:error, :invalid_code}
    end
  end

  def verify_code(_alive = false, _), do: {:error, :invalid_code}
  def verify_code(_alive = true, pid) do
    Process.send(pid, {:verify, self(), pid}, [])
    receive do
      :ok -> :ok
      {:error, _} -> {:error, :invalid_code}
    after
      1000 ->
        {:error, :timeout}
    end
  end

  def code_checker() do
    pid = self()
    receive do
      {:verify, from, ^pid} ->
        Process.send(from, :ok, [])
      {:verify, from, invalid} ->
        Process.send(from, {:error, invalid}, [])
    after
      10_000 ->
        :die
    end
  end

  def encode_pid(pid) do
    code = pid |> :erlang.term_to_binary()
    mac = :crypto.hmac(:sha256, @hmackey, code)
    Base.encode64(<<code::binary,mac::binary>>)
  end

  def decode_pid(code) do
    binary_code = Base.decode64!(code)
    data_size = byte_size(binary_code) - 32
    <<data::bytes-size(data_size),mac::bytes-size(32)>> = binary_code
    case secure_compare(:crypto.hmac(:sha256, @hmackey, data), mac) do
      true ->
        {:ok, :erlang.binary_to_term(data, [:safe])}
      false ->
        {:error, :invalid_code}
    end
  end

  def secure_compare(a, b) do
    # This should use a secury hash compare. There is one built-in in
    # erlang > 23 or Plug.Crypto has one. For demonstration purposes:
    a == b # This allows timing attacks so don't do it
  end

end

and to run it

iex(1)> c = OneTimeGenerator.create_code()
"g2dkAA1ub25vZGVAbm9ob3N0AAAAxwAAAAAA/yYJ7Hmp9rykBEKnl2Zj22XkZa4u6fdRZV/HXhf9ENI="
iex(2)> OneTimeGenerator.verify_code(c)
:ok
iex(3)> OneTimeGenerator.verify_code(c)
{:error, :invalid_code}
iex(4)> c = OneTimeGenerator.create_code()
"g2dkAA1ub25vZGVAbm9ob3N0AAAAzAAAAAAAJhODibe0ocK/vYVetGKfO3dEVOmMXxDX7dhbE1QHQcI="
iex(5)> Process.sleep(10_000)
:ok
iex(6)> OneTimeGenerator.verify_code(c)
{:error, :invalid_code}
iex(7)>
7 Likes

You could, alternatively, use a TOTP or HOTP with a very-long expiry time, or some form of shorter JWT, where you encode the expiry date into the hashed value, along with a signature to verify integrity. Then all your code has to do is check that the time is valid

I’ve done both in the past and found them suitable for password reset use cases

2 Likes

I used Veil which has some of this logic in it. I like the idea of keeping the browser open like @albydarned mentioned though: tightly couples the requester to another access provider (their email service).

2 Likes

If you use this method you can use Phoenix.Token. When you call verify you can specify a max_age. You could encode the users id into the token and look up the user when changing the password.

If you don’t record the token somewhere though And mark it as expired it will be technically valid forever and you’ll be relying on specifying a max_age every time.

1 Like

Thank you for your detailed reply, I had exams so I was kinda off coding for a while. I think I implemented a similar approach but without Genserver. You’re reply really inspired me! I

I have been assigned One Time Link as a test project for my semester (no coding just process flow and make wireframes). Can anyone assist me?

1 Like

I think all the replies have enough info about your question. However, I will briefly tell you how you can start, you need to have a small app with a controller linked to one Database table. Simply the table to link the generated links with users (if you have any) and save the expiry time of the link (if need it). In the controller you should have a hash string generator with high entropy. Every time you will generate a link and save the generated hash string in the database with the expiry and the user for future reference (if any). Basically every time some user access the link you should validate that the link is associated to this user and didn’t expire yet. I think the wireframes is easy enough. If you need further help don’t hesitate to contact me. :slight_smile:

do you have any flowchart available?