A closure with Erlang

December 7, 2016

I recently hit a programming task where a closure was the perfect solution. I describe the task, define a closure, and then present the solution.

A closeup of a robins nest with four blue eggs. class=
© 2016 Ian Baldwin for Unsplash

What’s the problem?

The basic authentication middleware for Elli is configured with an authorization function that takes three parameters: the request, the username and the password. You configure the middleware with an auth_fun parameter, is returned by the auth_fun(Config) in the code below:

handle(Req, Config) ->
    {User, Password} = credentials(Req),

    case apply(auth_fun(Config), [Req, User, Password]) of
        unauthorized ->
            throw({401,
                   [{<<"WWW-Authenticate">>, auth_realm(Config)}],
                   <<"Unauthorized">>});

        forbidden ->
            throw({403, [], <<"Forbidden">>});

        hidden ->
            throw({404, [], <<>>});

        _ ->
            ignore
    end.

For example, a simple auth function that secures every resource is:

auth_fun(_Req, User, Password) ->
  case {User, Password} of
    case {User, Password} of
        {undefined, undefined}      -> unauthorized;
        {<<"admin">>, <<"secret">>} -> ok;
        {User, Password}            -> forbidden
    end.

How to remove the hard coded user/password without changing the function signature?

What is a closure?

The most simple way to think of a closure is a function that can be stored as a variable (referred to as a "first-class function"), that has a special ability to access other variables local to the scope it was created in.

Here’s an example in JavaScript.

var name = "mark";
var f = function() { alert("Hello " + name);};
f();

The variable name is defined and that variable is in scope and is used by the function f. We could pass the variable f to another function and the variable name will still be in scope. It is “enclosed” by the function.

And here is that code running:

Screen shot of running above code in Safari JavaScript console.

And the equivalent code in Erlang.

~$ erl
Erlang/OTP 19 [erts-8.0.1] [source-ca40008] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V8.0.1  (abort with ^G)
1> Name = "mark".
"mark"
2> F = fun() -> io:format("Hello ~s~n", [Name]) end.
#Fun
3> F().
Hello mark
ok
4> 

The Solution

A function that accepts credentials as it’s argument.

The build_auth function takes a list of {user, password} tuples. For example, [ {<<"admin">>, <<"secret">>} ].

build_auth(Passwords) ->
    fun (_Req, undefined, undefined) -> unauthorized;
        (_Req, User, Password) ->
            case lists:member({User, Password}, Passwords) of 
              true -> ok;
              _ -> forbidden
            end
    end.

The syntax here is pretty interesting—the returned anonymous function uses pattern matching.

The Elli middleware config code.

Here’s the final configuration of the middleware in the supervisor:

init(_Args) ->
    Passwords = load_passwords(),
    BasicauthConfig = [{auth_fun, build_auth(Passwords)},
                       {auth_realm, <<"Admin Area">>}],
    MiddlewareConfig = [{mods,
                         [{elli_basicauth, BasicauthConfig},
                          {http_metric_handler, []}, {rest_handler, []}]}],
    ElliOpts = [{callback, elli_middleware},
                {callback_args, MiddlewareConfig}, {port, env:port()}],
    ElliSpec = {metrics_http,
                {elli, start_link, [ElliOpts]}, permanent, 5000, worker,
                [elli]},
    {ok, {{one_for_one, 5, 10}, [ElliSpec]}}.

The load_passwords function is responsible for reading the credentials from disk.

Note that this example is not production code.

In this example, the passwords are stored in disk in clear text. In production, at the very least I would use super long random passwords and hash them in the on-disk secrets file. And perhaps rate-limit failed login attempts.


If you see an error or something that could be improved, please let me know. This is a blog about me learning, so I expect I will get some stuff wrong. The best way to reach me is by email: mkbucc1234@gmail.com (after deleting all the numbers).

To make a comment, check for a thread on the erlang subreddit and if there isn't one, then start one up.

Follow on Twitter: @mbucc

Back to the index.