diff --git a/classes/CallbackHandler.php b/classes/CallbackHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..de88c9e88153c36c0a043f7fc93d6954a3387a8c
--- /dev/null
+++ b/classes/CallbackHandler.php
@@ -0,0 +1,67 @@
+<?php
+
+/* ----------------------------------------------------------------------------
+ *               INAF - National Institute for Astrophysics
+ *               IRA  - Radioastronomical Institute - Bologna
+ *               OATS - Astronomical Observatory - Trieste
+ * ----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2016 Istituto Nazionale di Astrofisica
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License Version 3 as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+namespace RAP;
+
+class CallbackHandler {
+
+    /**
+     * returns null if the callback URL is not listed in configuration file.
+     */
+    public static function getCallbackTitle($callbackURL) {
+
+        if ($callbackURL === null) {
+            return "Account Management";
+        }
+
+        global $CALLBACKS;
+
+        foreach ($CALLBACKS as $callback) {
+            if ($callback['url'] === $callbackURL) {
+                return $callback['title'];
+            }
+        }
+
+        throw new \Exception("Unauthorized callback URL");
+    }
+
+    public static function manageLoginRedirect($user) {
+
+        global $BASE_PATH, $session;
+
+        if (isset($session->callback) && $session->callback !== null) {
+            // External login using token
+            $token = Util::createNewToken();
+            DAO::get()->insertTokenData($token, $user->id);
+            header('Location: ' . $session->callback . '?token=' . $token);
+        } else {
+            // Login in session
+            $session->user = $user;
+            $session->save();
+            // Return to index
+            header('Location: ' . $BASE_PATH);
+        }
+    }
+
+}
diff --git a/classes/DAO.php b/classes/DAO.php
index ccbabd8da1301d281064c2621212f34ff0d22d89..8a9d0789f7eb5fa76b2d669f0b6bcdb2ae96bfaf 100644
--- a/classes/DAO.php
+++ b/classes/DAO.php
@@ -28,11 +28,11 @@ abstract class DAO {
 
     public abstract function getDBHandler();
 
-    public abstract function insertTokenData($token, $data);
+    public abstract function createLoginToken($token, $userId);
 
-    public abstract function findTokenData($token);
+    public abstract function findLoginToken($token);
 
-    public abstract function deleteToken($token);
+    public abstract function deleteLoginToken($token);
 
     /**
      * Return the new identity ID.
@@ -45,7 +45,7 @@ abstract class DAO {
     public abstract function createUser();
 
     public abstract function findUserById($userId);
-    
+
     /**
      * Return a User object, null if nothing was found.
      * @param type $type Identity type (EDU_GAIN, X509, GOOGLE, ...)
@@ -54,8 +54,12 @@ abstract class DAO {
      */
     public abstract function findUserByIdentity($type, $identifier, $dbIdentifier);
 
+    public abstract function searchUser($searchText);
+
     public abstract function addEmailToUser($email, $userId);
 
+    public abstract function createJoinRequest($token, $applicantUserId, $targetUserId);
+
     public $config;
 
     public function __construct($config) {
@@ -63,12 +67,13 @@ abstract class DAO {
     }
 
     public static function get() {
-        $config = parse_ini_file(ROOT . '/config.ini', true);
-        switch ($config['dbtype']) {
+        global $DATABASE;
+
+        switch ($DATABASE['dbtype']) {
             case 'MySQL':
-                return new MySQLDAO($config);
+                return new MySQLDAO($DATABASE);
             default:
-                throw new \Exception($config['dbtype'] . ' not supported yet');
+                throw new \Exception($DATABASE['dbtype'] . ' not supported yet');
         }
     }
 
diff --git a/classes/Identity.php b/classes/Identity.php
index e792c17a3649ea5d46dc73ad0b8363e494f02e63..c611494dd7b1472c1bc755a70fe127c1adc159e2 100644
--- a/classes/Identity.php
+++ b/classes/Identity.php
@@ -43,7 +43,7 @@ class Identity {
     /**
      * One of the types specified above. Mandatory field.
      */
-    private $type;
+    public $type;
 
     /**
      * Data related to specific account type (shibboleth persistent id, facebook id, etc, ...). Mandatory field.
@@ -101,8 +101,4 @@ class Identity {
         $this->type = $userType;
     }
 
-    public function getType() {
-        return $this->type;
-    }
-
 }
diff --git a/classes/MySQLDAO.php b/classes/MySQLDAO.php
index e484c927b3e5d72fb5abf7ac955907ba40a7eb9b..05ba51a75a2afb6d38ec3656612934d49c4bb650 100644
--- a/classes/MySQLDAO.php
+++ b/classes/MySQLDAO.php
@@ -33,16 +33,16 @@ class MySQLDAO extends DAO {
         return new PDO($connectionString, $this->config['username'], $this->config['password']);
     }
 
-    public function insertTokenData($token, $data) {
+    public function createLoginToken($token, $userId) {
 
         global $log;
 
         $dbh = $this->getDBHandler();
-        $stmt = $dbh->prepare("INSERT INTO token (token, data) VALUES(:token, :data)");
+        $stmt = $dbh->prepare("INSERT INTO login_token (token, data) VALUES(:token, :data)");
 
         $params = array(
             ':token' => $token,
-            ':data' => $data
+            ':data' => $userId
         );
 
         if ($stmt->execute($params)) {
@@ -53,11 +53,11 @@ class MySQLDAO extends DAO {
         }
     }
 
-    public function findTokenData($token) {
+    public function findLoginToken($token) {
 
         $dbh = $this->getDBHandler();
 
-        $stmt = $dbh->prepare("SELECT data FROM token WHERE token = :token AND CURRENT_TIMESTAMP < TIMESTAMPADD(MINUTE,1,creation_time)");
+        $stmt = $dbh->prepare("SELECT data FROM login_token WHERE token = :token AND CURRENT_TIMESTAMP < TIMESTAMPADD(MINUTE,1,creation_time)");
         $stmt->bindParam(':token', $token);
 
         $stmt->execute();
@@ -69,11 +69,11 @@ class MySQLDAO extends DAO {
         return null;
     }
 
-    public function deleteToken($token) {
+    public function deleteLoginToken($token) {
 
         $dbh = $this->getDBHandler();
 
-        $stmt = $dbh->prepare("DELETE FROM token WHERE token = :token");
+        $stmt = $dbh->prepare("DELETE FROM login_token WHERE token = :token");
         $stmt->bindParam(':token', $token);
         $stmt->execute();
     }
@@ -86,7 +86,7 @@ class MySQLDAO extends DAO {
                 . " VALUES(:user_id, :type, :email, :name, :surname, :institution, :username, :local_db_id, :typed_id, :eppn)");
 
         $stmt->bindParam(':user_id', $userId);
-        $stmt->bindParam(':type', $identity->getType());
+        $stmt->bindParam(':type', $identity->type);
         $stmt->bindParam(':email', $identity->email);
         $stmt->bindParam(':name', $identity->name);
         $stmt->bindParam(':surname', $identity->surname);
@@ -111,6 +111,23 @@ class MySQLDAO extends DAO {
         return $dbh->lastInsertId();
     }
 
+    private function getIdentityByRow($row) {
+
+        $identity = new Identity($row['type']);
+
+        $identity->id = $row['id'];
+        $identity->typedId = $row['typed_id'];
+        $identity->email = $row['email'];
+        $identity->localDBId = $row['local_db_id'];
+        $identity->name = $row['name'];
+        $identity->surname = $row['surname'];
+        $identity->institution = $row['institution'];
+        $identity->username = $row['username'];
+        $identity->eppn = $row['eppn'];
+
+        return $identity;
+    }
+
     public function findUserById($userId) {
 
         $dbh = $this->getDBHandler();
@@ -121,20 +138,16 @@ class MySQLDAO extends DAO {
         $stmt->bindParam(':user_id', $userId);
         $stmt->execute();
 
+        $result = $stmt->fetchAll();
+        if (count($result) === 0) {
+            return null;
+        }
+
         $user = new User();
         $user->id = $userId;
 
-        foreach ($stmt->fetchAll() as $row) {
-            $identity = new Identity($row['type']);
-            $identity->id = $row['id'];
-            $identity->typedId = $row['typed_id'];
-            $identity->email = $row['email'];
-            $identity->localDBId = $row['local_db_id'];
-            $identity->name = $row['name'];
-            $identity->surname = $row['surname'];
-            $identity->institution = $row['institution'];
-            $identity->username = $row['username'];
-            $identity->eppn = $row['eppn'];
+        foreach ($result as $row) {
+            $identity = $this->getIdentityByRow($row);
             $user->addIdentity($identity);
         }
 
@@ -152,15 +165,25 @@ class MySQLDAO extends DAO {
 
         $dbh = $this->getDBHandler();
 
-        $stmt = $dbh->prepare("SELECT user_id FROM identity WHERE type = :type AND typed_id = :typed_id AND local_db_id = :local_db_id");
+        $query = "SELECT user_id FROM identity WHERE type = :type AND typed_id = :typed_id";
+        if (isset($dbIdentifier) && $dbIdentifier !== null) {
+            $query .= " AND local_db_id = :local_db_id";
+        }
+
+        $stmt = $dbh->prepare($query);
         $stmt->bindParam(':type', $type);
         $stmt->bindParam(':typed_id', $identifier);
-        $stmt->bindParam(':local_db_id', $dbIdentifier);
+        if (isset($dbIdentifier) && $dbIdentifier !== null) {
+            $stmt->bindParam(':local_db_id', $dbIdentifier);
+        }
 
         $stmt->execute();
 
         $result = $stmt->fetchAll();
 
+        global $log;
+        $log->debug("count = " . count($result));
+
         if (count($result) === 0) {
             return null;
         }
@@ -172,6 +195,55 @@ class MySQLDAO extends DAO {
         return $this->findUserById($userId);
     }
 
+    public function searchUser($searchText) {
+
+        $dbh = $this->getDBHandler();
+
+        // TODO: Add additional email search...
+
+        $query = "SELECT `user_id`, `id`, `type`, `typed_id`, `email`, `local_db_id`, `name`, `surname`, `institution`, `username`, `eppn`"
+                . " FROM identity WHERE `email` LIKE :email OR `name` LIKE :name OR `surname` LIKE :surname";
+
+        $stmt = $dbh->prepare($query);
+
+        $searchParam = $searchText . '%';
+        $stmt->bindParam(':email', $searchParam);
+        $stmt->bindParam(':name', $searchParam);
+        $stmt->bindParam(':surname', $searchParam);
+
+        $stmt->execute();
+
+        $userMap = array();
+
+        //global $log;
+        //$log->debug('In searchUser');
+
+        foreach ($stmt->fetchAll() as $row) {
+
+            //$log->debug($row['user_id']);
+
+            $identity = $this->getIdentityByRow($row);
+            //$log->debug(json_encode($identity));
+
+            $userId = $row['user_id'];
+            if (array_key_exists($userId, $userMap)) {
+                $user = $userMap[$userId];
+            } else {
+                $user = new User();
+                $user->id = $userId;
+                $userMap[$userId] = $user;
+            }
+
+            array_push($user->identities, $identity);
+        }
+
+        $users = [];
+        foreach ($userMap as $userId => $user) {
+            array_push($users, $user);
+        }
+        return $users;
+    }
+
     public function addEmailToUser($email, $userId) {
 
         $dbh = $this->getDBHandler();
@@ -183,4 +255,18 @@ class MySQLDAO extends DAO {
         $stmt->execute();
     }
 
+    public function createJoinRequest($token, $applicantUserId, $targetUserId) {
+
+        $dbh = $this->getDBHandler();
+
+        $stmt = $dbh->prepare("INSERT INTO `join_request`(`token`, `applicant_user_id`, `target_user_id`)"
+                . " VALUES(:token, :applicant_user_id, :target_user_id)");
+
+        $stmt->bindParam(':token', $token);
+        $stmt->bindParam(':applicant_user_id', $applicantUserId);
+        $stmt->bindParam(':target_user_id', $targetUserId);
+
+        $stmt->execute();
+    }
+
 }
diff --git a/classes/SessionData.php b/classes/SessionData.php
index c6d807741a67a3433cfad618e6c567d136e87590..b2a885ea5f9529bf29c597bf4f7af44010c84dfc 100644
--- a/classes/SessionData.php
+++ b/classes/SessionData.php
@@ -26,8 +26,10 @@ namespace RAP;
 
 class SessionData {
 
-    public $callback;
+    private $callbackURL;
+    private $callbackTitle;
     public $user;
+    public $userSearchResults;
 
     public function save() {
         $_SESSION['SessionData'] = $this;
@@ -41,4 +43,34 @@ class SessionData {
         }
         return $_SESSION['SessionData'];
     }
+
+    public function setCallbackURL($callbackURL) {
+        $this->callbackURL = $callbackURL;
+        $this->callbackTitle = CallbackHandler::getCallbackTitle($callbackURL);
+        $this->save();
+    }
+
+    public function getCallbackURL() {
+        return $this->callbackURL;
+    }
+
+    public function getCallbackTitle() {
+        return $this->callbackTitle;
+    }
+
+    public function searchUser($searchText) {
+        $users = DAO::get()->searchUser($searchText);
+
+        $this->userSearchResults = [];
+        foreach ($users as $user) {
+            // this search shouldn't contains the user itself
+            if ($user->id !== $this->user->id) {
+                $searchResult = UserSearchResult::buildFromUser($user);
+                array_push($this->userSearchResults, $searchResult);
+            }
+        }
+
+        $this->save();
+    }
+
 }
diff --git a/classes/TokenHandler.php b/classes/TokenHandler.php
index c096a01710e9f52969e21bb8f25a311cd34804fc..4c7998ade71c855226a52c1a943644c5f50a347f 100644
--- a/classes/TokenHandler.php
+++ b/classes/TokenHandler.php
@@ -29,6 +29,7 @@ class TokenHandler {
     public static function createNewToken($data) {
         $token = bin2hex(openssl_random_pseudo_bytes(16)); // http://stackoverflow.com/a/18890309/771431
         DAO::get()->insertTokenData($token, $data);
+        return $token;
     }
 
     public static function deleteToken($token) {
diff --git a/classes/UserSearchResult.php b/classes/UserSearchResult.php
new file mode 100644
index 0000000000000000000000000000000000000000..daada02ea3d19caadf4e04cd3b6c512c74454640
--- /dev/null
+++ b/classes/UserSearchResult.php
@@ -0,0 +1,78 @@
+<?php
+
+/* ----------------------------------------------------------------------------
+ *               INAF - National Institute for Astrophysics
+ *               IRA  - Radioastronomical Institute - Bologna
+ *               OATS - Astronomical Observatory - Trieste
+ * ----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2016 Istituto Nazionale di Astrofisica
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License Version 3 as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+namespace RAP;
+
+class UserSearchResult {
+
+    private $userId;
+    public $userDisplayText;
+
+    public static function buildFromUser(User $user) {
+        $usr = new UserSearchResult();
+        $usr->userId = $user->id;
+
+        $nameAndSurname = null;
+        $email = null;
+        $identityTypes = [];
+        foreach ($user->identities as $identity) {
+            array_push($identityTypes, $identity->type);
+            if ($nameAndSurname === null && $identity->name !== null && $identity->surname !== null) {
+                $nameAndSurname = $identity->name . ' ' . $identity->surname;
+            }
+            if ($email === null) {
+                $email = $identity->email;
+            }
+        }
+
+        // Building display text string        
+        $displayText = "";
+
+        if ($nameAndSurname !== null) {
+            $displayText .= $nameAndSurname;
+        } else {
+            $displayText .= $email;
+        }
+
+        $displayText .= ' (';
+        $firstIdentity = true;
+        foreach ($identityTypes as $type) {
+            if (!$firstIdentity) {
+                $displayText .= '+';
+            }
+            $displayText .= $type;
+            $firstIdentity = false;
+        }
+        $displayText .= ')';
+
+        $usr->userDisplayText = $displayText;
+
+        return $usr;
+    }
+
+    public function getUserId() {
+        return $this->userId;
+    }
+
+}
diff --git a/classes/Util.php b/classes/Util.php
new file mode 100644
index 0000000000000000000000000000000000000000..9a786b1a9f4aba5a7a057cc9250043daee978504
--- /dev/null
+++ b/classes/Util.php
@@ -0,0 +1,37 @@
+<?php
+
+/* ----------------------------------------------------------------------------
+ *               INAF - National Institute for Astrophysics
+ *               IRA  - Radioastronomical Institute - Bologna
+ *               OATS - Astronomical Observatory - Trieste
+ * ----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2016 Istituto Nazionale di Astrofisica
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License Version 3 as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+namespace RAP;
+
+/**
+ * Utility class
+ */
+class Util {
+
+    public static function createNewToken() {
+        // Credits: http://stackoverflow.com/a/18890309/771431
+        return bin2hex(openssl_random_pseudo_bytes(16));
+    }
+
+}
diff --git a/config.ini b/config.ini
deleted file mode 100644
index 2065065c528b35849d8c65f91530a3e4de6b91f8..0000000000000000000000000000000000000000
--- a/config.ini
+++ /dev/null
@@ -1,8 +0,0 @@
-
-;connection_string = mysql:host=localhost;dbname=rap"
-dbtype = MySQL
-hostname = localhost
-port = 3306
-username = rap
-password = ***REMOVED***
-dbname = rap
diff --git a/config.php b/config.php
index 732e9d8aa72c26518821172f5a4a035525ac10f7..53d270460b050d208cb0bd061f6c7305b8ef6826 100644
--- a/config.php
+++ b/config.php
@@ -31,20 +31,44 @@ $BASE_PATH = $PROTOCOL . $_SERVER['HTTP_HOST'] . $CONTEXT_ROOT;
 $LOG_PATH = ROOT . "/logs/rap-service.log";
 $LOG_LEVEL = Monolog\Logger::DEBUG;
 
-$PDO = array(
-    "connection_string" => "mysql:host=localhost;dbname=rap",
-    "user" => "rap",
-    "password" => "***REMOVED***"
-);
-
-$Google = array(
-    'id' => "***REMOVED***.apps.googleusercontent.com",
-    'secret' => "***REMOVED***",
-    'callback' => $BASE_PATH . "/oauth2/google_token.php");
+$CALLBACKS = [
+    array(
+        'url' => 'http://localhost:8087/grouper',
+        'title' => 'Login to Grouper'
+    )
+];
 
-$Facebook = array(
-    'id' => "***REMOVED***",
-    'secret' => "***REMOVED***",
-    'version' => "v2.2",
-    'callback' => $BASE_PATH . "/oauth2/facebook_token.php");
+$DATABASE = array(
+    'dbtype' => 'MySQL',
+    'hostname' => 'localhost',
+    'port' => 3306,
+    'username' => 'rap',
+    'password' => '***REMOVED***',
+    'dbname' => 'rap'
+);
 
+$AUTHENTICATION_METHODS = array(
+    'eduGAIN' => array(),
+    'Google' => array(
+        'id' => "***REMOVED***.apps.googleusercontent.com",
+        'secret' => "***REMOVED***",
+        'callback' => $BASE_PATH . "/oauth2/google_token.php"),
+    'Facebook' => array(
+        'id' => "***REMOVED***",
+        'secret' => "***REMOVED***",
+        'version' => "v2.2",
+        'callback' => $BASE_PATH . "/oauth2/facebook_token.php"),
+    'LinkedIn' => array(),
+    'X.509' => array(),
+    'Direct' => array(
+        array(
+            'name' => 'IA2',
+            'label' => '',
+            'logo' => 'ia2-logo-60x60.png',
+            'description' => 'Use the IA2 logo if you have an IA2 account (provided by IA2 or self-registered)',
+            'type' => 'ldap',
+            'ldap_user_scope' => 'ou=custom_users,dc=oats,dc=inaf,dc=it',
+            'ldap_user_id_field' => 'uid'
+        )
+    )
+);
diff --git a/css/chosen.min.css b/css/chosen.min.css
new file mode 100644
index 0000000000000000000000000000000000000000..a264b6d8797a223fa398fad195f07149c4d22088
--- /dev/null
+++ b/css/chosen.min.css
@@ -0,0 +1,3 @@
+/* Chosen v1.7.0 | (c) 2011-2017 by Harvest | MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md */
+
+.chosen-container{position:relative;display:inline-block;vertical-align:middle;font-size:13px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.chosen-container *{box-sizing:border-box}.chosen-container .chosen-drop{position:absolute;top:100%;z-index:1010;width:100%;border:1px solid #aaa;border-top:0;background:#fff;box-shadow:0 4px 5px rgba(0,0,0,.15);clip:rect(0,0,0,0)}.chosen-container.chosen-with-drop .chosen-drop{clip:auto}.chosen-container a{cursor:pointer}.chosen-container .search-choice .group-name,.chosen-container .chosen-single .group-name{margin-right:4px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-weight:400;color:#999}.chosen-container .search-choice .group-name:after,.chosen-container .chosen-single .group-name:after{content:":";padding-left:2px;vertical-align:top}.chosen-container-single .chosen-single{position:relative;display:block;overflow:hidden;padding:0 0 0 8px;height:25px;border:1px solid #aaa;border-radius:5px;background-color:#fff;background:linear-gradient(#fff 20%,#f6f6f6 50%,#eee 52%,#f4f4f4 100%);background-clip:padding-box;box-shadow:0 0 3px #fff inset,0 1px 1px rgba(0,0,0,.1);color:#444;text-decoration:none;white-space:nowrap;line-height:24px}.chosen-container-single .chosen-default{color:#999}.chosen-container-single .chosen-single span{display:block;overflow:hidden;margin-right:26px;text-overflow:ellipsis;white-space:nowrap}.chosen-container-single .chosen-single-with-deselect span{margin-right:38px}.chosen-container-single .chosen-single abbr{position:absolute;top:6px;right:26px;display:block;width:12px;height:12px;background:url(chosen-sprite.png) -42px 1px no-repeat;font-size:1px}.chosen-container-single .chosen-single abbr:hover{background-position:-42px -10px}.chosen-container-single.chosen-disabled .chosen-single abbr:hover{background-position:-42px -10px}.chosen-container-single .chosen-single div{position:absolute;top:0;right:0;display:block;width:18px;height:100%}.chosen-container-single .chosen-single div b{display:block;width:100%;height:100%;background:url(chosen-sprite.png) no-repeat 0 2px}.chosen-container-single .chosen-search{position:relative;z-index:1010;margin:0;padding:3px 4px;white-space:nowrap}.chosen-container-single .chosen-search input[type=text]{margin:1px 0;padding:4px 20px 4px 5px;width:100%;height:auto;outline:0;border:1px solid #aaa;background:url(chosen-sprite.png) no-repeat 100% -20px;font-size:1em;font-family:sans-serif;line-height:normal;border-radius:0}.chosen-container-single .chosen-drop{margin-top:-1px;border-radius:0 0 4px 4px;background-clip:padding-box}.chosen-container-single.chosen-container-single-nosearch .chosen-search{position:absolute;clip:rect(0,0,0,0)}.chosen-container .chosen-results{color:#444;position:relative;overflow-x:hidden;overflow-y:auto;margin:0 4px 4px 0;padding:0 0 0 4px;max-height:240px;-webkit-overflow-scrolling:touch}.chosen-container .chosen-results li{display:none;margin:0;padding:5px 6px;list-style:none;line-height:15px;word-wrap:break-word;-webkit-touch-callout:none}.chosen-container .chosen-results li.active-result{display:list-item;cursor:pointer}.chosen-container .chosen-results li.disabled-result{display:list-item;color:#ccc;cursor:default}.chosen-container .chosen-results li.highlighted{background-color:#3875d7;background-image:linear-gradient(#3875d7 20%,#2a62bc 90%);color:#fff}.chosen-container .chosen-results li.no-results{color:#777;display:list-item;background:#f4f4f4}.chosen-container .chosen-results li.group-result{display:list-item;font-weight:700;cursor:default}.chosen-container .chosen-results li.group-option{padding-left:15px}.chosen-container .chosen-results li em{font-style:normal;text-decoration:underline}.chosen-container-multi .chosen-choices{position:relative;overflow:hidden;margin:0;padding:0 5px;width:100%;height:auto;border:1px solid #aaa;background-color:#fff;background-image:linear-gradient(#eee 1%,#fff 15%);cursor:text}.chosen-container-multi .chosen-choices li{float:left;list-style:none}.chosen-container-multi .chosen-choices li.search-field{margin:0;padding:0;white-space:nowrap}.chosen-container-multi .chosen-choices li.search-field input[type=text]{margin:1px 0;padding:0;height:25px;outline:0;border:0!important;background:transparent!important;box-shadow:none;color:#999;font-size:100%;font-family:sans-serif;line-height:normal;border-radius:0;width:25px}.chosen-container-multi .chosen-choices li.search-choice{position:relative;margin:3px 5px 3px 0;padding:3px 20px 3px 5px;border:1px solid #aaa;max-width:100%;border-radius:3px;background-color:#eee;background-image:linear-gradient(#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);background-size:100% 19px;background-repeat:repeat-x;background-clip:padding-box;box-shadow:0 0 2px #fff inset,0 1px 0 rgba(0,0,0,.05);color:#333;line-height:13px;cursor:default}.chosen-container-multi .chosen-choices li.search-choice span{word-wrap:break-word}.chosen-container-multi .chosen-choices li.search-choice .search-choice-close{position:absolute;top:4px;right:3px;display:block;width:12px;height:12px;background:url(chosen-sprite.png) -42px 1px no-repeat;font-size:1px}.chosen-container-multi .chosen-choices li.search-choice .search-choice-close:hover{background-position:-42px -10px}.chosen-container-multi .chosen-choices li.search-choice-disabled{padding-right:5px;border:1px solid #ccc;background-color:#e4e4e4;background-image:linear-gradient(#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);color:#666}.chosen-container-multi .chosen-choices li.search-choice-focus{background:#d4d4d4}.chosen-container-multi .chosen-choices li.search-choice-focus .search-choice-close{background-position:-42px -10px}.chosen-container-multi .chosen-results{margin:0;padding:0}.chosen-container-multi .chosen-drop .result-selected{display:list-item;color:#ccc;cursor:default}.chosen-container-active .chosen-single{border:1px solid #5897fb;box-shadow:0 0 5px rgba(0,0,0,.3)}.chosen-container-active.chosen-with-drop .chosen-single{border:1px solid #aaa;border-bottom-right-radius:0;border-bottom-left-radius:0;background-image:linear-gradient(#eee 20%,#fff 80%);box-shadow:0 1px 0 #fff inset}.chosen-container-active.chosen-with-drop .chosen-single div{border-left:0;background:transparent}.chosen-container-active.chosen-with-drop .chosen-single div b{background-position:-18px 2px}.chosen-container-active .chosen-choices{border:1px solid #5897fb;box-shadow:0 0 5px rgba(0,0,0,.3)}.chosen-container-active .chosen-choices li.search-field input[type=text]{color:#222!important}.chosen-disabled{opacity:.5!important;cursor:default}.chosen-disabled .chosen-single{cursor:default}.chosen-disabled .chosen-choices .search-choice .search-choice-close{cursor:default}.chosen-rtl{text-align:right}.chosen-rtl .chosen-single{overflow:visible;padding:0 8px 0 0}.chosen-rtl .chosen-single span{margin-right:0;margin-left:26px;direction:rtl}.chosen-rtl .chosen-single-with-deselect span{margin-left:38px}.chosen-rtl .chosen-single div{right:auto;left:3px}.chosen-rtl .chosen-single abbr{right:auto;left:26px}.chosen-rtl .chosen-choices li{float:right}.chosen-rtl .chosen-choices li.search-field input[type=text]{direction:rtl}.chosen-rtl .chosen-choices li.search-choice{margin:3px 5px 3px 0;padding:3px 5px 3px 19px}.chosen-rtl .chosen-choices li.search-choice .search-choice-close{right:auto;left:4px}.chosen-rtl.chosen-container-single .chosen-results{margin:0 0 4px 4px;padding:0 4px 0 0}.chosen-rtl .chosen-results li.group-option{padding-right:15px;padding-left:0}.chosen-rtl.chosen-container-active.chosen-with-drop .chosen-single div{border-right:0}.chosen-rtl .chosen-search input[type=text]{padding:4px 5px 4px 20px;background:url(chosen-sprite.png) no-repeat -30px -20px;direction:rtl}.chosen-rtl.chosen-container-single .chosen-single div b{background-position:6px 2px}.chosen-rtl.chosen-container-single.chosen-with-drop .chosen-single div b{background-position:-12px 2px}@media only screen and (-webkit-min-device-pixel-ratio:1.5),only screen and (min-resolution:144dpi),only screen and (min-resolution:1.5dppx){.chosen-rtl .chosen-search input[type=text],.chosen-container-single .chosen-single abbr,.chosen-container-single .chosen-single div b,.chosen-container-single .chosen-search input[type=text],.chosen-container-multi .chosen-choices .search-choice .search-choice-close,.chosen-container .chosen-results-scroll-down span,.chosen-container .chosen-results-scroll-up span{background-image:url(chosen-sprite@2x.png)!important;background-size:52px 37px!important;background-repeat:no-repeat!important}}
\ No newline at end of file
diff --git a/css/style.css b/css/style.css
new file mode 100644
index 0000000000000000000000000000000000000000..f44bc67f44b4bf00b74b952b30650633980f908d
--- /dev/null
+++ b/css/style.css
@@ -0,0 +1,123 @@
+body {
+    background-color: #eee;
+    padding-bottom: 150px;
+}
+
+@keyframes pulse {
+    from {
+        transform: scale(1, 1);
+    }
+
+    to {
+        transform: scale(1.07, 1.07);
+    }
+}
+
+.home-box {
+    float: left;
+    width: 240px;
+    height: 165px;
+    padding: 2px;
+    margin: 10px;
+    border-radius: 15px;
+    border: 1px solid #ccc;
+    background-color: #fff;
+    padding: 4px 8px 8px 8px;
+    box-shadow: 0 1px 2px rgba(0,0,0,.1);
+}
+
+.home-box .img-wrapper {
+    width: 100%;
+    height: 80px;
+    text-align: center;
+    display: table;
+}
+
+.home-box .img-wrapper a {
+    display: table-cell;
+    vertical-align: middle;
+}
+
+.home-box .img-wrapper a:hover {
+    animation-duration: 0.2s;
+    animation-fill-mode: both;
+    animation-name: pulse;
+    animation-timing-function: ease-in;
+}
+
+#main-header {
+    background-image: url('../img/rap-background.jpg');
+    background-position: center; 
+    background-repeat: repeat-x;
+    height: 90px;
+    position: relative;
+    border-bottom: 1px #9c9c9c solid;
+    margin-bottom: 20px;
+    box-shadow: 0 1px 2px rgba(0,0,0,.1);
+    text-shadow: 0 2px 3px rgba(0,0,0,.6);
+}
+
+.credits {
+    position: absolute;
+    top: 66px;
+    bottom: 0;
+    right: 11px;
+    left: 0;
+    text-align: right;
+    font-size: 12px;
+    color: #fff;    
+}
+
+.page-title-wrapper {
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    right: 0;
+    left: 0;
+}
+
+.page-title-wrapper h1 {
+    color: #fff;
+    font-weight: bold;
+    font-size: 42px;
+}
+
+#main-footer-wrapper {
+    position: fixed;
+    bottom: 0;
+    right: 0;
+    left: 0;
+    background-color: #eee;
+}
+
+#main-footer {
+    color: #666;
+    border-top: 1px #d4d4d4 solid;
+    background-color: #f8f8f8;
+    padding: 5px 0;
+}
+
+#footer-credits {
+    font-size: 13px;
+    color: #666;
+    margin-bottom: 8px
+}
+
+.callback-title {
+    margin-top: 0;
+    font-weight: bold;
+    color: #24388e;
+    text-shadow: 0 2px 3px rgba(0,0,0,.35);
+}
+
+.panel {
+    box-shadow: 0 1px 2px rgba(0,0,0,.1);
+}
+
+.panel-default {
+    border-color: #ccc
+}
+
+.panel-default > .panel-heading {
+    background-image: linear-gradient(to bottom,#f5f5f5 0,#ccc 100%);
+}
diff --git a/img/ia2-logo-60x60.png b/img/ia2-logo-60x60.png
new file mode 100644
index 0000000000000000000000000000000000000000..f93ef2495577ed01ad2a5ac25578607dd9891670
Binary files /dev/null and b/img/ia2-logo-60x60.png differ
diff --git a/img/logo-ia2-small.png b/img/logo-ia2-small.png
new file mode 100644
index 0000000000000000000000000000000000000000..cb913c9d1bfb1815de802febddfdec6dd2b56552
Binary files /dev/null and b/img/logo-ia2-small.png differ
diff --git a/img/rap-background.jpg b/img/rap-background.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..538bb0ba4d26db032bdf97fa41d9ea6c21c5cc81
Binary files /dev/null and b/img/rap-background.jpg differ
diff --git a/include/footer.php b/include/footer.php
index 9943ff0f851844f5daaccd7e1adca02a193936dc..0de7f71286b14074892fc0ca7fe0144ed099a376 100644
--- a/include/footer.php
+++ b/include/footer.php
@@ -1,3 +1,11 @@
 </div>
+<footer id="main-footer-wrapper" class="text-center">
+    <p id="footer-credits">This software has been adapted by the IA2 team from the Remote Authentication Portal written by Franco Tinarelli at INAF-IRA.</p>
+    <div id="main-footer">
+        Powered by
+        <img src="img/logo-ia2-small.png" alt="logo IA2" />
+        <a href="http://www.ia2.inaf.it/" target="blank_">IA2</a>
+    </div>
+</footer>
 </body>
 </html>
diff --git a/include/front-controller.php b/include/front-controller.php
new file mode 100644
index 0000000000000000000000000000000000000000..28c26903c7951c8e86a48fd96d0a407d14ed5f90
--- /dev/null
+++ b/include/front-controller.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * Front Controller using http://flightphp.com/
+ * In all these calls user session must exist, so we have to start it at the 
+ * beginning using the startSession() function.
+ */
+//
+
+function setCallback() {
+    global $session;
+
+    $callback = Flight::request()->data['callback'];
+    $session->setCallbackURL(isset($callback) ? $callback : null);
+}
+
+Flight::route('/', function() {
+    startSession();
+    setCallback();
+    global $session, $AUTHENTICATION_METHODS;
+    Flight::render('index.php', array('title' => 'RAP',
+        'session' => $session, 'auth' => $AUTHENTICATION_METHODS));
+});
+
+Flight::route('GET /logout', function() {
+    startSession();
+    session_destroy();
+    Flight::redirect('/');
+});
+
+Flight::route('/google', function() {
+    startSession();
+    Flight::redirect('/oauth2/google_token.php');
+});
+
+Flight::route('/facebook', function() {
+    startSession();
+    Flight::redirect('/oauth2/facebook_login.php');
+});
diff --git a/include/gui-backend.php b/include/gui-backend.php
new file mode 100644
index 0000000000000000000000000000000000000000..6087bb3277fd8abaf3529aa0d07c55c3b658e518
--- /dev/null
+++ b/include/gui-backend.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * REST backend for JavaScript code.
+ */
+//
+
+function checkSession() {
+
+    startSession();
+
+    global $session;
+    if ($session->user === null) {
+        http_response_code(401);
+        die("You must be registered to perform this action");
+    }
+}
+
+Flight::route('GET /user', function() {
+
+    checkSession();
+    global $session;
+
+    $searchText = Flight::request()->query['search'];
+    $session->searchUser($searchText);
+
+    $jsRes = [];
+    foreach ($session->userSearchResults as $searchResult) {
+        array_push($jsRes, $searchResult->userDisplayText);
+    }
+
+    echo json_encode($jsRes);
+});
+
+Flight::route('POST /join', function() {
+
+    checkSession();
+    global $session;
+
+    $selectedUserIndex = Flight::request()->data['selectedUserIndex'];
+    $targetUserId = $session->userSearchResults[$selectedUserIndex]->getUserId();
+
+    $token = RAP\Util::createNewToken();
+    RAP\DAO::get()->createJoinRequest($token, $session->user->id, $targetUserId);
+
+    echo "";
+});
diff --git a/include/header.php b/include/header.php
index 92c810d6804db1bcd19937e8f5a83fd149166a48..29356dd43f6fc35a546977657bbd11615e9c09d9 100644
--- a/include/header.php
+++ b/include/header.php
@@ -7,6 +7,16 @@
         <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous" />
         <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
         <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
+        <link rel="stylesheet" href="css/style.css" />
+        <script src="js/script.js"></script>
     </head>
     <body>
+        <header id="main-header">
+            <div class="credits">
+                Image Credits & Copyright: Colombari/E.Recurt
+            </div>
+            <div class="page-title-wrapper">
+                <h1 class="text-center">Remote Authentication Portal</h1>
+            </div>
+        </header>
         <div class="container">
diff --git a/include/init.php b/include/init.php
index e6124cf334fb2bd854a60655bfc38b845626d3c6..cfb824c825299a2ea4d47908c6fb0517ffa4a0c7 100644
--- a/include/init.php
+++ b/include/init.php
@@ -40,5 +40,8 @@ include ROOT . '/config.php';
 $log = new Monolog\Logger('mainLogger');
 $log->pushHandler(new Monolog\Handler\StreamHandler($LOG_PATH, $LOG_LEVEL));
 
-session_start();
-$session = RAP\SessionData::get();
+function startSession() {
+    session_start();
+    global $session;
+    $session = RAP\SessionData::get();
+}
diff --git a/include/rest-web-service.php b/include/rest-web-service.php
new file mode 100644
index 0000000000000000000000000000000000000000..e913e8a70c73feb2bfd76d9f780e3e7e4102439f
--- /dev/null
+++ b/include/rest-web-service.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * REST Web Service using http://flightphp.com/
+ */
+//
+$WS_PREFIX = '/ws';
+
+Flight::route('GET ' . $WS_PREFIX . '/user-info', function() {
+
+    $token = Flight::request()->query['token'];
+    $userData = RAP\DAO::get()->findTokenData($token);
+
+    if (is_null($userData)) {
+        http_response_code(404);
+        die("Token not found");
+    }
+
+    RAP\DAO::get()->deleteToken($token);
+
+    header('Content-Type: text/plain');
+    echo $userData;
+});
+
+Flight::route('GET ' . $WS_PREFIX . '/user/@userId', function($userId) {
+
+    $user = RAP\DAO::get()->findUserById($userId);
+    if ($user !== null) {
+        header('Content-Type: application/json');
+        echo json_encode($user);
+    } else {
+        http_response_code(404);
+        die("User not found");
+    }
+});
+
+Flight::route('GET ' . $WS_PREFIX . '/user', function() {
+
+    $searchText = Flight::request()->query['search'];
+    $users = RAP\DAO::get()->searchUser($searchText);
+    echo json_encode($users);
+});
diff --git a/index.php b/index.php
index 8241bc3ffe5b8cc06cb4b91d4b73845a5d01e16d..754f719c6ba324aeb4dd06185284f4094c3d769c 100644
--- a/index.php
+++ b/index.php
@@ -24,71 +24,9 @@
 
 include './include/init.php';
 
-/**
- * REST Web Service using http://flightphp.com/
- */
-Flight::route('/demo', function() {
-    $callback = (isset($_SERVER['HTTPS']) ? "https" : "http") . '://' . $_SERVER['HTTP_HOST'] . "/rap-service/user-info";
-    Flight::render('demo.php', array('callback' => $callback));
-});
-
-Flight::route('/', function() {
-    global $session;
-    Flight::render('index.php', array('title' => 'RAP', 'session' => $session));
-});
-
-Flight::route('GET /logout', function() {
-    session_destroy();
-    Flight::redirect('/');
-});
-
-Flight::route('/google', function() {
-    global $session;
-
-    $callback = Flight::request()->data['callback'];
-    $session->callback = isset($callback) ? $callback : null;
-    $session->save();
-
-    Flight::redirect('/oauth2/google_token.php');
-});
-
-Flight::route('POST /facebook', function() {
-    $callback = Flight::request()->data['callback'];
-    if (!isset($callback)) {
-        throw new Exception("Callback URL not set!");
-    }
-    session_start();
-    $_SESSION['rap_callback'] = $callback;
-    Flight::redirect('/oauth2/facebook_login.php');
-});
-
-Flight::route('GET /user-info', function() {
-
-    $token = Flight::request()->query['token'];
-    $userData = RAP\DAO::getTokenData($token);
-
-    if (is_null($userData)) {
-        http_response_code(404);
-        die("Token not found");
-    }
-
-    RAP\DAO::deleteToken($token);
-
-    header('Content-Type: application/json');
-    echo $userData;
-});
-
-Flight::route('GET /user/@userId', function($userId) {
-
-    $user = RAP\DAO::get()->findUserById($userId);
-    if ($user !== null) {
-        global $log;
-        $log->debug(count($user->identities));
-        echo json_encode($user);
-    } else {
-        http_response_code(404);
-        die("User not found");
-    }
-});
+include './include/front-controller.php';
+include './include/gui-backend.php';
+include './include/rest-web-service.php';
 
+// Starting Flight framework
 Flight::start();
diff --git a/js/script.js b/js/script.js
new file mode 100644
index 0000000000000000000000000000000000000000..2ee30793e57f581b539ac38f219a31fc289db004
--- /dev/null
+++ b/js/script.js
@@ -0,0 +1,48 @@
+(function ($) {
+
+    // function factory used to generate function to be executed at timeout (see below)
+    function searchUserFactory(searchText) {
+        return function () {
+            $.get('user?search=' + searchText, function (response) {
+                var users = JSON.parse(response);
+
+                // Display the selector only if we have some results
+                $('#user-selector-group').toggleClass('hide', users.length === 0);
+
+                // Fill the user selector
+                $userSelector = $('#user-selector-group select');
+                $userSelector.empty();
+                for (var i = 0; i < users.length; i++) {
+                    $userSelector.append('<option value="' + i + '">' + users[i] + '</option>');
+                }
+            });
+        };
+    }
+
+    function sendJoinRequest() {
+        $userSelector = $('#user-selector-group select');
+        var selectedUserIndex = $userSelector.val();
+        if (selectedUserIndex !== null) {
+            $.post('join', {selectedUserIndex: selectedUserIndex}, function (response) {
+                console.log(response);
+            });
+        }
+    }
+
+    // When the document is loaded
+    $(document).ready(function () {
+
+        // Add keyup event handler on user search input text
+        var timeoutId = 0;
+        $(document).on('keyup', '#user-search-text', function (event) {
+            clearTimeout(timeoutId);
+            var searchUser = searchUserFactory($(event.target).val());
+            // wait 500 ms without typing before doing the AJAX call
+            timeoutId = setTimeout(searchUser, 500);
+        });
+
+        // Add click event handler to join request button
+        $(document).on('click', '#send-join-request-btn', sendJoinRequest);
+    });
+
+})(jQuery);
\ No newline at end of file
diff --git a/oauth2/facebook_login.php b/oauth2/facebook_login.php
index a970bb25a26a17d0321fa8f49b7faec3f7b8d9af..e89404eddf27bfb84feb2b8b447be209348b4060 100755
--- a/oauth2/facebook_login.php
+++ b/oauth2/facebook_login.php
@@ -23,6 +23,9 @@
  */
 
 include '../include/init.php';
+startSession();
+
+$Facebook = $AUTHENTICATION_METHODS['Facebook'];
 
 $fb = new Facebook\Facebook([
     'app_id' => $Facebook['id'],
@@ -35,6 +38,5 @@ $helper = $fb->getRedirectLoginHelper();
 $permissions = ['email']; // Optional permissions
 $loginUrl = $helper->getLoginUrl($Facebook['callback'], $permissions);
 
-
 header("Location: $loginUrl");
 ?>
diff --git a/oauth2/facebook_token.php b/oauth2/facebook_token.php
index 8a41986b43e8711d397d573af5fd84048cd3bb41..7e05a14fc6a7f4c0c482f6d9e2b4220076683c24 100755
--- a/oauth2/facebook_token.php
+++ b/oauth2/facebook_token.php
@@ -23,13 +23,9 @@
  */
 
 include '../include/init.php';
+startSession();
 
-session_start();
-$callback = $_SESSION['rap_callback'];
-if (!isset($callback)) {
-    http_response_code(422);
-    die("Callback URL not set!");
-}
+$Facebook = $AUTHENTICATION_METHODS['Facebook'];
 
 $fb = new Facebook\Facebook([
     'app_id' => $Facebook['id'],
@@ -78,20 +74,27 @@ try {
     exit;
 }
 
-$user = $response->getGraphUser();
-
 $_SESSION['fb_access_token'] = (string) $accessToken;
 
-// Creating user object
-$userData = array(
-    "type" => "Facebook",
-    "name" => $user["first_name"],
-    "surname" => $user["last_name"],
-    "emailAddresses" => [$user["email"]],
-    "typed_id" => $user["id"]
-);
-
-$token = RAP\DAO::insertLogin($userData);
-header('Location: ' . $callback . '?token=' . $token);
-die();
+$fbUser = $response->getGraphUser();
+
+$typedId = $fbUser["id"];
+
+$user = RAP\UserHandler::findUserByIdentity(RAP\Identity::FACEBOOK, $typedId, null);
+
+if ($user === null) {
+    $user = new RAP\User();
+
+    $identity = new RAP\Identity(RAP\Identity::FACEBOOK);
+    $identity->email = $fbUser["email"];
+    $identity->name = $fbUser["first_name"];
+    $identity->surname = $fbUser["last_name"];
+    $identity->typedId = $typedId;
+
+    $user->addIdentity($identity);
+
+    RAP\UserHandler::saveUser($user);
+}
+
+RAP\CallbackHandler::manageLoginRedirect($user);
 ?>
diff --git a/oauth2/google_token.php b/oauth2/google_token.php
index 553bbb03666660be22538b21baedd354312dce6c..990ee84b9e76584665fb101b5f9342033808c364 100644
--- a/oauth2/google_token.php
+++ b/oauth2/google_token.php
@@ -23,6 +23,9 @@
  */
 
 include '../include/init.php';
+startSession();
+
+$Google = $AUTHENTICATION_METHODS['Google'];
 
 $client = new Google_Client(array(
     'client_id' => $Google['id'],
@@ -87,17 +90,7 @@ if ($client->getAccessToken()) {
         RAP\UserHandler::saveUser($user);
     }
 
-    if (isset($session->callback) && $session->callback !== null) {
-        // External login using token
-        $token = RAP\TokenHandler::createNewToken($user->id);
-        header('Location: ' . $session->callback . '?token=' . $token);
-    } else {
-        // Login in session
-        $session->user = $user;
-        $session->save();
-        // Return to index
-        header('Location: ' . $BASE_PATH);
-    }
+    RAP\CallbackHandler::manageLoginRedirect($user);
 
     die();
 } else {
diff --git a/sql/setup-database.sql b/sql/setup-database.sql
index 5ea15e11748c60a6028e6f7f272b2f1ae2a4a909..cff418a55e4cf7dcf68b8d0132b2c94331104b7c 100644
--- a/sql/setup-database.sql
+++ b/sql/setup-database.sql
@@ -27,7 +27,7 @@ CREATE TABLE `additional_email` (
   FOREIGN KEY (`user_id`) REFERENCES `user`(`id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
-CREATE TABLE `token` (
+CREATE TABLE `login_token` (
   `id` bigint(20) NOT NULL AUTO_INCREMENT,
   `token` varchar(255) NOT NULL,
   `data` text,
@@ -35,9 +35,20 @@ CREATE TABLE `token` (
   PRIMARY KEY (`id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
-CREATE EVENT tokens_cleanup
+CREATE TABLE `join_request` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `token` varchar(255) NOT NULL,
+  `applicant_user_id` bigint(20) NOT NULL,
+  `target_user_id` bigint(20) NOT NULL,
+  `creation_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`),
+  FOREIGN KEY (`applicant_user_id`) REFERENCES `user`(`id`),
+  FOREIGN KEY (`target_user_id`) REFERENCES `user`(`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE EVENT login_tokens_cleanup
     ON SCHEDULE
       EVERY 1 MINUTE
-    COMMENT 'Remove expired tokens'
+    COMMENT 'Remove expired login tokens'
     DO
-      DELETE FROM token WHERE CURRENT_TIMESTAMP > TIMESTAMPADD(MINUTE,1,creation_time);
+      DELETE FROM login_token WHERE CURRENT_TIMESTAMP > TIMESTAMPADD(MINUTE,1,creation_time);
diff --git a/views/demo.php b/views/demo.php
deleted file mode 100644
index 7048eb49e9dc966fef3a770ddf4ef0fefdb6c65a..0000000000000000000000000000000000000000
--- a/views/demo.php
+++ /dev/null
@@ -1,16 +0,0 @@
-<!DOCTYPE html>
-<html>
-    <head>
-        <title>RAP Service</title>
-    </head>
-    <body>
-        <form method="POST" action="google">
-            <input type="hidden" name="callback" value="<?php echo $callback; ?>" />
-            <input type="submit" value="Google" />
-        </form>
-        <form method="POST" action="facebook">
-            <input type="hidden" name="callback" value="<?php echo $callback; ?>" />
-            <input type="submit" value="Facebook" />
-        </form>
-    </body>
-</html>
\ No newline at end of file
diff --git a/views/index.php b/views/index.php
index 52a446c8e5a0ad43994a137df6ba0ff5033440f7..5a27057e4d0140edb905b53fd92886f2dd0b1ff9 100644
--- a/views/index.php
+++ b/views/index.php
@@ -3,12 +3,155 @@ include 'include/header.php';
 ?>
 
 <?php if ($session->user === null) { ?>
-    <a href="google">
-        <img src="img/google-60.png" alt="Google Logo" />
-    </a>
+    <div class="row">
+        <div class="col-xs-12">
+            <h1 class="text-center callback-title"><?php echo $session->getCallbackTitle(); ?></h1>
+        </div>
+    </div>
+    <div class="row">
+        <div class="col-xs-12">
+            <?php if (isset($auth['eduGAIN'])) { ?>
+                <div class="home-box">
+                    <div class="img-wrapper">
+                        <a href="edugain">
+                            <img src="img/eduGain-200.png" alt="eduGAIN Logo" />
+                        </a>
+                    </div>
+                    Use the eduGAIN Logo to Login or Register to the RAP facility if you belong to an eduGAIN IdP.
+                </div>
+            <?php } ?>
+            <?php if (isset($auth['Google']) || isset($auth['Facebook']) || isset($auth['LinkedIn'])) { ?>
+                <div class="home-box">
+                    <div class="img-wrapper">
+                        <?php if (isset($auth['Google'])) { ?>
+                            <a href="google" class="animated pulse">
+                                <img src="img/google-60.png" alt="Google Logo" />
+                            </a>
+                        <?php } ?>
+                        <?php if (isset($auth['Facebook'])) { ?>
+                            <a href="facebook">
+                                <img src="img/facebook-60.png" alt="Facebook Logo" />
+                            </a>
+                        <?php } ?>
+                        <?php if (isset($auth['LinkedIn'])) { ?>
+                            <a href="linkedin">
+                                <img src="img/linkedin-60.png" alt="LinkedIn Logo" />
+                            </a>
+                        <?php } ?>
+                    </div>
+                    Use these Logos to Login or Register to the RAP facility with your social identity
+                </div>
+            <?php } ?>
+            <?php if (isset($auth['X.509'])) { ?>
+                <div class="home-box">
+                    <div class="img-wrapper">
+                        <a href="x509">
+                            <img src="img/x509-200.png" alt="X.509 Logo" />
+                        </a>
+                    </div>
+                    Use the X.509 Logo to Login with your personal certificate (IGTF and TERENA-TACAR, are allowed).
+                </div>
+            <?php } ?>
+            <?php
+            if (isset($auth['Direct'])) {
+                foreach ($auth['Direct'] as $directAuth) {
+                    ?>
+                    <div class="home-box">
+                        <div class="img-wrapper">
+                            <a href="#">
+                                <img src="img/<?php echo $directAuth['logo']; ?>" alt="" />
+                            </a>
+                        </div>
+                        <?php echo $directAuth['description']; ?>
+                    </div>
+                    <?php
+                }
+            }
+            ?>
+        </div>
+    </div>
 <?php } else { ?>
-    <?php echo json_encode($session->user); ?>
-    <a href="logout">Logout</a>
+    <div class="row">
+        <div class="col-sm-5 col-xs-12">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Your identities</h3>
+                </div>
+                <div class="panel-body">
+                    <?php foreach ($session->user->identities as $identity) { ?>
+                        <dl class="dl-horizontal">
+                            <dt>Type</dt>
+                            <dd><?php echo $identity->type; ?></dd>
+                            <dt>E-mail</dt>
+                            <dd><?php echo $identity->email; ?></dd>
+                            <?php if ($identity->eppn !== null) { ?>
+                                <dt>EduPersonPrincipalName</dt>
+                                <dd><?php echo $identity->eppn; ?></dd>
+                            <?php } ?>
+                            <?php if ($identity->username !== null) { ?>
+                                <dt>Username</dt>
+                                <dd><?php echo $identity->username; ?></dd>
+                            <?php } ?>
+                            <?php if ($identity->name !== null) { ?>
+                                <dt>Name</dt>
+                                <dd><?php echo $identity->name; ?></dd>
+                            <?php } ?>
+                            <?php if ($identity->surname !== null) { ?>
+                                <dt>Surname</dt>
+                                <dd><?php echo $identity->surname; ?></dd>
+                            <?php } ?>
+                            <?php if ($identity->institution !== null) { ?>
+                                <dt>Institution</dt>
+                                <dd><?php echo $identity->institution; ?></dd>
+                            <?php } ?>
+                        </dl>
+                    <?php } ?>
+                </div>
+            </div>
+        </div>
+        <div class="col-sm-2 text-center">
+            <button class="btn btn-success" type="button" data-toggle="modal" data-target="#search-user-modal">
+                Join with another identity
+            </button>
+        </div>
+        <div class="col-sm-5">
+            <a href="logout" class="btn btn-primary pull-right">Logout</a>
+        </div>
+    </div>
+
+    <div class="modal fade" id="search-user-modal" tabindex="-1" role="dialog" aria-labelledby="search-user-modal-title">
+        <div class="modal-dialog" role="document">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+                    <h4 class="modal-title" id="search-user-modal-title">Search user</h4>
+                </div>
+                <div class="modal-body">
+                    <form class="form-horizontal">
+                        <div class="form-group">
+                            <label for="user-search-text" class="col-xs-3 control-label">Search text</label>
+                            <div class="col-xs-9">
+                                <input type="text" class="form-control" id="user-search-text" placeholder="Name, surname or email...">
+                            </div>
+                        </div>
+                        <div class="form-group hide" id="user-selector-group">
+                            <label for="user-selector" class="col-xs-3 control-label">Select user</label>
+                            <div class="col-xs-9">
+                                <select id="user-selector" class="form-control">
+
+                                </select>
+                            </div>
+                        </div>
+                    </form>
+                </div>
+                <div class="modal-footer">
+                    <button class="btn btn-primary" type="button" id="send-join-request-btn">
+                        Send join request
+                    </button>
+                </div>
+            </div>
+        </div>
+    </div>
 <?php } ?>
 
 <?php