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.
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:
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.
- If both user/pass are undefined, return unauthorized (first pattern).
- If user/pass is a valid password, return ok (second pattern).
- If neither of those cases match, return forbidden (second pattern).
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