How to generate a random url safe string with Elixir

I need to be able to generate random url safe strings so I could use those in links (like in an activation link sent to a user's email), so how can I generate it? Is there a way to do that only with Elixir or I'd have to use some library?


Solution 1:

What you can do instead is to generate a Base64-encoded string to be used as a confirmation token. This confirmation token will then be saved to your DB and passed as params to the activation link. Your activation url would look something like:

activation_url(MyApp.Endpoint, :confirm, confirm_id: confirm_id)

The above url helper assumes you have a MyApp.ActivationController and a confirm/2 action in that controller. To generate the confirm_id, you could do:

def random_string(length) do
  :crypto.strong_rand_bytes(length) |> Base.url_encode64 |> binary_part(0, length)
end

# random_string(64)

In your MyApp.ActivationController.confirm/2, you could have code lik:

def confirm(conn, %{"confirm_id" => confirm_id}) do
  user = Repo.get_by(User, confirm_id: confirm_id)
  User.confirm(user)
  conn
  |> put_flash(:info, "Account confirmed!")
  |> redirect(to: "/")
end

Hope that helps!

Solution 2:

You can easily define a module to do this. In this example, @chars determines what characters appear in your generated strings.

defmodule StringGenerator do
  @chars "ABCDEFGHIJKLMNOPQRSTUVWXYZ" |> String.split("")

  def string_of_length(length) do
    Enum.reduce((1..length), [], fn (_i, acc) ->
      [Enum.random(@chars) | acc]
    end) |> Enum.join("")
  end
end

StringGenerator.string_of_length(3) # => "YCZ"

Solution 3:

As noted in @JimGray's comment, your specification should really be in terms of the amount of entropy you want to represent by the random URL safe strings. Something along the lines of "I need N bits" because someone told you to use N bits, or "I want to avoid repeat in N strings and I can accept a risk of 1 in n of a collision". Either way, it's directly about entropy and only indirectly about string length.

For example, be sure that if you use a solution like @Gjaldon' answer you understand even though 512 bits of randomness is used, the amount of entropy for the actual string generated by random_string(64) is 320 bits. Whether that's sufficient is of course dependent on your scenario, which as noted above is probably best expressed as, for example, "I need a million strings with no more than a 1 in a trillion risk of repeat", in which case 320 bits is gross overkill as you'd only need 79.

If you want more control and understanding of generating random strings, look at EntropyString. With that library, you could do something like the following to get a string with 256 bits of entropy:

iex> defmodule Id, do: use EntropyString, charset: charset64
iex> Id.token
"ziKYK7t5LzVYn5XiJ_jYh30KxCCsLorRXqLwwEnZYHJ"

Or if you realize a million strings with a repeat risk of 1 in a trillion is sufficient, you could set up your Id generation like:

iex> defmodule Id do
...>   use EntropyString, charset: charset64
...>   @bits entropy_bits(1.0e6, 1.0e12)
...>   def random, do: Id.random_string(@bits)
...> end
iex> Id.random
"FhlGVXOaXV9f3f"

Either way, control and understanding are nice things to have.