Tutorial Step 5 - Authenticating with e_auth e_component
In this tutorial step we will learn how to use e_components - Erlang Web's components mechanism for extending the framework capabilities. We will add the authentication layer to all of the admin functionalities.
Contents
Installing e_auth and e_auth_dets
Instead of writing our own application/module for managing the users we should consider using one of the existing components. Fortunately, there is a component called e_auth - it provides a simple interface to the abstract authentication engine. One of the engine's implementation - e_auth_dets is also available as an e_component. Let's use them in our project! (the components are described here)
In order to search for some e_component we should run:
$ ./bin/e_component.erl search auth e_auth-1.0 Authentication and authorization base interface. e_auth_dets-1.0 Authorization and authentication DETS e_auth implementation e_auth_sample-1.0 A sample implementation of the e_auth interface.
At first we must install the interface, then the particular implementation - e_auth_dets-1.0:
./bin/e_component.erl install e_auth-1.0 x lib/e_auth-1.0/Emakefile x lib/e_auth-1.0/ebin/e_auth.app x lib/e_auth-1.0/doc/overview.edoc x lib/e_auth-1.0/src/e_auth.erl x lib/e_auth-1.0/src/e_auth_user.erl x lib/e_auth-1.0/src/e_auth_group.erl e_auth-1.0 e_component installed successfully $ ./bin/e_component.erl install e_auth_dets-1.0 x lib/e_auth_dets-1.0/Emakefile x lib/e_auth_dets-1.0/ebin/e_auth_dets.app x lib/e_auth_dets-1.0/doc/overview.edoc x lib/e_auth_dets-1.0/test/e_conf.erl x lib/e_auth_dets-1.0/test/e_auth_dets_test.erl x lib/e_auth_dets-1.0/src/e_auth_dets.erl e_auth_dets-1.0 e_component installed successfully
Great! The installer updated also the Emakefile for us, so all we need to do is to compile the applications:
$ ./bin/compile.erl
Now we should put those e_components in our project's configuration file. Open the config/project.conf and insert there the following entry:
1 {ecomponents, [{e_auth, []},
2 {e_auth_dets, []}]}.
Then start the service in the normal way (bin/start_interactive). If you check for the running applications you should see both e_auth and e_auth_dets among them:
1 1> application:which_applications().
2 [{e_auth_dets,"Authorization and authentication DETS e_auth implementation",
3 "1.0"},
4 {e_auth,"Authorization and authentication interface","1.0"},
5 {inets,"INETS CXC 138 49","5.0.12"},
6 {wparts,"Wpart Components","1.3"},
7 {wpart,"Wpart","1.3"},
8 {eptic,"Eptic Erlang Web application","1.3"},
9 {mnesia,"MNESIA CXC 138 12","4.4.7"},
10 {crypto,"CRYPTO version 1","1.5.3"},
11 {ssl,"Erlang/OTP SSL application","3.10"},
12 {sasl,"SASL CXC 138 11","2.1.5.4"},
13 {stdlib,"ERTS CXC 138 10","1.15.5"},
14 {kernel,"ERTS CXC 138 10","2.12.5"}]
Creating a login controller
Because we must check if user is really the one who he is pretending to be, we must ask him for a correct credentials: a login/password pair. Let's create a simple controller for this reason:
$ ./bin/generate.erl controller --app shop --name login Exported functions (separated with ,): login, logout File (...)/lib/shop-0.1/src/login.erl created successfully!
1 -module(login).
2 -export([login/1, logout/1]).
3
4 login(_Args) ->
5 Username = wpart:fget("post:username"),
6 Password = wpart:fget("post:password"),
7
8 case e_auth:login(Username, Password) of
9 ok ->
10 {template, "login/successful.html"};
11 {error, Reason} ->
12 wpart:fset("error", Reason),
13 {template, "login/unsuccessful.html"}
14 end.
15
16 logout(_Args) ->
17 e_auth:logout(),
18 {redirect, "/item/all"}.
In the login function we assume that we will get the credentials from the POST request variables. The rest is on the e_auth component side - it will do the thing and return us the result of the operation. It will also update the session of the user, so it will be possible to check its status at any time using e_auth:status/1 function.
Because the authentication is a stateful action (if not we will have to enter the credentials each time we enter the site) we need to keep its state between the requests. In order to do this, e_auth uses session request variable internally. session is in fact a container for other variables - the only difference is that its content is persistent (for some time). Thanks to that we are able to store some information in one request and retrieve it during the another one - such as the information about the authentication status.
Configuring the dispatcher, creating the templates
When we have the controller, we must link it with some URL. Edit the config/dispatch.conf file then and enter the following lines:
1 %%
2 %% LOGIN URLs
3 %%
4 {static, "^/login", "login/login.html"}.
5 {dynamic, "^/do_login", {login, login}}.
6 {dynamic, "^/logout", {login, logout}}.
The next step is to create the templates. We should start from the login/login.html file that will display the simple login form for us:
<html>
<head>
<title>Login page</title>
</head>
<body>
<form action="/do_login" method="post" accept-charset="utf-8">
Login: <wpart:string name="username" />
Password: <wpart:password name="password" />
<input type="submit" value="Login" />
</form>
</body>
</html>The authentication layer is almost ready for use! Let's check the form by entering the http://localhost:8080/login:
The last two templates are:
login/successful.html:
<html> <head> <title>Login successful</title> </head> <body> <h1>You have log in successfully!</h1> </body> </html>
login/unsuccessful.html:
<html> <head> <title>Login error</title> </head> <body> <h2>An error occured during the login process.</h2> <h3>Reason: <wpart:lookup key="error" format="term"/></h3> </body> </html>
Adding new user
The last part before the testing is to add the user to the database. Let's use the e_auth API and run the following command from the Erlang shell:
1 2> e_auth:add_user("admin", "admin").
2 ok
Wow - right now we have created a brand new admin user - identified by password "admin".
Let's test the login controller correctness by entering the admin user's data into the login form. If everything is ok, we should see
Otherwise, an error page should be rendered:
Creating authenticate annotation
Right now we have all bricks needed to build an authorization system for our shop. We can edit the item_admin.erl file and insert the authorization check in each function. But do you remember annotations? It will be much easier and cleaner to use them. Edit the shop_utils.erl file and add the following piece of code:
1 -export([authorize/4]).
2
3 ...
4
5 ?BEFORE.
6 authorize(_AnnArg, _Mod, _Fun, Args) ->
7 case e_auth:status() of
8 true ->
9 {proceed, Args};
10 false ->
11 {skip, {redirect, "/login"}}
12 end.
Compile the sources with ./bin/compile.erl and add the annotate with AUTHORIZE all the functions within item_admin controller (let it be the first annotation in row - before validation and existence checks).
At the very beginning we have set the add_item entry in the dispatcher to be a static route (the one that does not access the controller but goes straight to the template). If we want to make some authorization before it, we should change it to be dynamic:
1 add(_Args) ->
2 {template, "item/add.html"}.
Since right now we do not have a roles in our system, we will not use the annotation argument. For example, the item_admin:add/1 function will look like:
1 ?AUTHORIZE(not_used).
2 ?VALIDATE({item, create}).
3 do_add(Item) ->
4 wtype_item:create(Item),
5
6 {redirect, "/item/all"}.
Compile the sources once again and enjoy the power of e_components and annotations!
Testing the service
Before we will say that this part of the tutorial is over, we should check if the authentication and authorization engine really works.
At first, we must be sure that we are logged out - visit http://localhost:8080/logout.
Then we should try to add a new item: http://localhost:8080/item/admin/add. We should be redirected to the login page. Let's enter the correct data (login "admin", password "admin"), click Login button and enter once again to the /item/admin/add page: hey - we are in!
And we should also test the opposite scenario - let's logout (http://localhost:8080/logout) and try to add something - we should be redirected to the login form.
e_auth component is a very powerful tool - it provides the user management API (create/delete/rename/change password) and user groups handling. The engine of the e_auth could be easily switched from DETS to e.g. some DBMS (mnesia/couchdb) or LDAP.
Download
This tutorial step is over, the package containing the sources of the application can be downloaded from here.
