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.

Tags: erlang functional