Closure for module configuration?

Hi,

When a user connects to my application via a Phoenix.Socket he/she provides a token (generated by another system) that I would like to verify and validate. I created a Token module to encapsulate all the verification and validation code. It uses Joken to actually do the job and it requires some configuration like creating a signer using a public_key and defining some validation functions for checking the claims (the iss for instance).

These two strings (public key and the expected issuer) are considered configuration and must be provided to the solution via environment variables.

Considering that this configuration part of the flow needs to be executed only once (when the application starts, for instance) and not every time a token is verified, I started wondering what would be the best design, i.e., how to perform the configuration only once and provide a simple verify_and_validate/1 function that takes only the token.

A closure seems to be an option. Example:

  def get_verify_fun(public_key, issuer) do
    config = token_config(issuer)
    signer = Joken.Signer.create("RS256", %{"pem" => public_key})
    fn token -> verify(config, token, signer) end
  end

  defp verify(config, token, signer) do
    with {:ok, claims} <- Joken.verify_and_validate(config, token, signer) do
      check_for_missing_claims(claims, @required_claims)
    end
  end

Now the question is: where to call the get_verify_fun/2 and/or how to make the returned function available to my code in the connect function in the Phoenix.Socket module?

An option is to call the get_verify_fun/2 in my runtime.exs, store the returned function in the application scope using config and use Application.fetch_env! in the socket connect:

Example (runtime.exs):

public_key = get_env("TOKEN_PUBLIC_KEY") |> String.replace("\\n", "\n")
issuer = get_env("TOKEN_ISSUER")
verify = MyApp.Token.get_verify_fun(public_key, issuer)
config :myapp, verify_token: verify

It works but it doesn’t feel right. What do you think?

I know I can fall back to passing the config along with the token on every call to verify but then we would be executing the configuration part over and over. It might be not a big deal in this case if Joken is not doing much, but what if this “configuration” ou “preparation” part of the flow is expensive?

Sorry for the long and not so direct to the point question and let me know if I can clarify anything.

Cheers!

It looks like You want to cache your function when application start.

Probably using an Agent would be ok.
Or an Ets table (and a GenServer), to speed up think…
Or cachex.
Or a simple file, with :erlang.term_to_binary() and binary_to_term().

Or just application configuration :wink:

2 Likes

But it needs to be dynamic, at least when app starts :slight_smile:

Application.put_env/3 call in Application.start/2 should do.

3 Likes

Ok, it’s simpler :slight_smile:

Thanks @kokolegorille, @hauleth, @dimitarvp and @gregvaughn for taking the time to read it and contribute.

Application.put_env/3 call in Application.start/2 should do.

Your suggestion is to store the resulting function in the application environment when it starts, right?

Isn’t it what I’m already doing, except that I’m using config in the runtime.exs?