diff --git a/auth/oauth2/linkedin_login.php b/auth/oauth2/linkedin_login.php
new file mode 100644
index 0000000000000000000000000000000000000000..df940e81b57d84b22a99132e701924b203e30d74
--- /dev/null
+++ b/auth/oauth2/linkedin_login.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.
+ */
+
+include '../../include/init.php';
+startSession();
+
+$LinkedIn = $AUTHENTICATION_METHODS['LinkedIn'];
+
+$url = "https://www.linkedin.com/oauth/v2/authorization?response_type=code";
+$url .= "&client_id=" . $LinkedIn['id'];
+$url .= "&redirect_uri=" . $LinkedIn['callback'];
+$url .= "&state=789654123";
+$url .= "&scope=r_basicprofile r_emailaddress";
+
+header("Location: $url");
+?>
diff --git a/auth/oauth2/linkedin_token.php b/auth/oauth2/linkedin_token.php
new file mode 100644
index 0000000000000000000000000000000000000000..d4e88e4732f6b26fe3d3bc8ec850ede492fcc814
--- /dev/null
+++ b/auth/oauth2/linkedin_token.php
@@ -0,0 +1,124 @@
+<?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.
+ */
+
+include '../../include/init.php';
+startSession();
+
+$LinkedIn = $AUTHENTICATION_METHODS['LinkedIn'];
+
+if (!isset($_REQUEST['code'])) {
+    die("Unable to get LinkedIn client code");
+}
+
+//create array of data to be posted to get AccessToken
+$post_data = array(
+    'grant_type' => "authorization_code",
+    'code' => $_REQUEST['code'],
+    'redirect_uri' => $LinkedIn['callback'],
+    'client_id' => $LinkedIn['id'],
+    'client_secret' => $LinkedIn['secret']);
+
+//traverse array and prepare data for posting (key1=value1)
+foreach ($post_data as $key => $value) {
+    $post_items[] = $key . '=' . $value;
+}
+
+//create the final string to be posted
+$post_string = implode('&', $post_items);
+
+//create cURL connection
+$conn1 = curl_init('https://www.linkedin.com/oauth/v2/accessToken');
+
+//set options
+curl_setopt($conn1, CURLOPT_CONNECTTIMEOUT, 30);
+curl_setopt($conn1, CURLOPT_RETURNTRANSFER, true);
+curl_setopt($conn1, CURLOPT_SSL_VERIFYPEER, true);
+curl_setopt($conn1, CURLOPT_FOLLOWLOCATION, 1);
+
+//set data to be posted
+curl_setopt($conn1, CURLOPT_POSTFIELDS, $post_string);
+
+//perform our request
+$result1 = curl_exec($conn1);
+
+if ($result1) {
+    $my_token = json_decode($result1, TRUE);
+    $access_token = $my_token['access_token'];
+    $expires_in = $my_token['expires_in'];
+    curl_close($conn1);
+} else {
+    //show information regarding the error
+    $errorMessage = curl_errno($conn1) . "-";
+    $errorMessage = $errorMessage . curl_error($conn1);
+    curl_close($conn1);
+    die($errorMessage);
+}
+
+// Call to API
+$conn2 = curl_init();
+curl_setopt($conn2, CURLOPT_URL, "https://api.linkedin.com/v1/people/~:(first-name,last-name,email-address,id)?format=json");
+curl_setopt($conn2, CURLOPT_HTTPHEADER, array(
+    'Authorization: Bearer ' . $access_token
+));
+
+curl_setopt($conn2, CURLOPT_RETURNTRANSFER, true);
+$result = curl_exec($conn2);
+
+if ($result) {
+    $data = json_decode($result, TRUE);
+
+    curl_close($conn2);
+
+    if (isset($data['errorCode'])) {
+        $errorMessage = $data['message'];
+        die($errorMessage);
+    }
+
+    $typedId = $data['id'];
+
+    $user = $userHandler->findUserByIdentity(RAP\Identity::LINKEDIN, $typedId);
+
+    if ($user === null) {
+        $user = new RAP\User();
+
+        $identity = new RAP\Identity(RAP\Identity::LINKEDIN);
+        $identity->email = $data['emailAddress'];
+        $identity->name = $data['firstName'];
+        $identity->surname = $data['lastName'];
+        $identity->typedId = $typedId;
+
+        $user->addIdentity($identity);
+
+        $userHandler->saveUser($user);
+    }
+
+    $callbackHandler->manageLoginRedirect($user, $session);
+} else {
+    //show information regarding the error
+    $errorMessage = curl_errno($conn2) . "-";
+    $errorMessage = $errorMessage . curl_error($conn2);
+    curl_close($conn2);
+    die($errorMessage);
+}
+?>
diff --git a/classes/CallbackHandler.php b/classes/CallbackHandler.php
index a569844e481c03492c11043410941c4e402b3aa9..bd48a0d8d8df9612cb8dc09c9496aa6998ae839c 100644
--- a/classes/CallbackHandler.php
+++ b/classes/CallbackHandler.php
@@ -39,7 +39,7 @@ class CallbackHandler {
     /**
      * returns null if the callback URL is not listed in configuration file.
      */
-    public static function getCallbackTitle($callbackURL) {
+    public function getCallbackTitle($callbackURL) {
 
         if ($callbackURL === null) {
             return "Account Management";
@@ -58,16 +58,22 @@ class CallbackHandler {
 
         if ($session->getCallbackURL() !== null) {
             // External login using token
-            $token = Util::createNewToken();
-            $this->dao->createLoginToken($token, $user->id);
-            header('Location: ' . $session->getCallbackURL() . '?token=' . $token);
+            header('Location: ' . $this->getLoginWithTokenURL($user->id, $session->getCallbackURL()));
+            die();
         } else {
             // Login in session
             $session->user = $user;
             $session->save();
             // Return to index
             header('Location: ' . $this->basePath);
+            die();
         }
     }
 
+    public function getLoginWithTokenURL($userId, $callbackURL) {
+        $token = Util::createNewToken();
+        $this->dao->createLoginToken($token, $userId);
+        return $callbackURL . '?token=' . $token;
+    }
+
 }
diff --git a/classes/MailSender.php b/classes/MailSender.php
index cede88a89c79eac54a6a0460884206e0bf49d2f0..606743db11c1d66ec77371851a9558420a7ae7cf 100644
--- a/classes/MailSender.php
+++ b/classes/MailSender.php
@@ -26,8 +26,60 @@ namespace RAP;
 
 class MailSender {
 
-    public static function sendJoinEmail(User $recipientUser, User $applicantUser) {
-        
+    private $serverName;
+    private $basePath;
+
+    public function __construct($serverName, $basePath) {
+        $this->serverName = $serverName;
+        $this->basePath = $basePath;
+    }
+
+    public function sendJoinEmail(User $recipientUser, User $applicantUser, $token) {
+
+        $subject = "IA2 RAP: Join request";
+
+        $header = "From: noreply@" . $this->serverName . "\r\n";
+        $header .= "Content-Type: text/html; charset=UTF-8";
+
+        $confirmJoinURL = $this->basePath . '/confirm-join?token=' . $token;
+
+        $body = "Dear IA2 user,<br/><br/>";
+        $body .= "the following user requested to join your accounts on the "
+                . "<a href=\"https://sso.ia2.inaf.it/rap-ia2/\" target=\"blank_\">RAP facility</a>:<br/><br/>";
+
+        foreach ($applicantUser->identities as $identity) {
+
+            $body .= "<b>Type</b>: " . $identity->type . "<br/>";
+
+            if ($identity->name !== null) {
+                $body .= "<b>Name</b>: " . $identity->name . "<br/>";
+            }
+
+            if ($identity->surname !== null) {
+                $body .= "<b>Surname</b>: " . $identity->surname . "<br/>";
+            }
+
+            $body .= "<b>E-mail</b>: " . $identity->email . "<br/>";
+
+            if ($identity->eppn !== null) {
+                $body .= "<b>Eppn</b>: " . $identity->eppn . "<br/>";
+            }
+
+            if ($identity->institution !== null) {
+                $body .= "<b>Institution</b>: " . $identity->institution . "<br/>";
+            }
+
+            $body .= "<br/>";
+        }
+
+        $body .= "<br/>If you and this user are the same person click on the following link for joining your accounts:<br/>";
+        $body .= "<a href=\"$confirmJoinURL\" target=\"blank_\">$confirmJoinURL</a>";
+        $body .= "<br/><br/>Otherwise you can ignore this email<br/><br/>";
+
+        $body .= "<b>*** This is an automatically generated email, please do not reply to this message ***</b><br/>";
+        $body .= "If you need information please contact <a href=\"mailto:ia2@oats.inaf.it\">IA2 Staff</a>";
+
+        mail($recipientUser->getPrimaryEmail(), $subject, $body, $header);
     }
 
 }
diff --git a/classes/MySQLDAO.php b/classes/MySQLDAO.php
index 536882d47c411dcdc51e81a8953d0cfb19ecaa0f..c5c5d9f215ba9919e938061f90613925708f39bf 100644
--- a/classes/MySQLDAO.php
+++ b/classes/MySQLDAO.php
@@ -246,6 +246,10 @@ class MySQLDAO implements DAO {
 
     public function createJoinRequest($token, $applicantUserId, $targetUserId) {
 
+        if($applicantUserId === $targetUserId) {
+            throw new \Exception("Invalid target user id");
+        }
+        
         $dbh = $this->getDBHandler();
 
         $stmt = $dbh->prepare("INSERT INTO `join_request`(`token`, `applicant_user_id`, `target_user_id`)"
diff --git a/config.php b/config.php
index 840befa7a49d356922dc238608aef6059e698396..e13a8c36da0d8c6edeb21eddcd5b5f1a8ca7bbf2 100644
--- a/config.php
+++ b/config.php
@@ -22,7 +22,7 @@
  * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  */
 
-$CONTEXT_ROOT = "/rap-service";
+$CONTEXT_ROOT = "/rap-ia2";
 $VERSION = "1.0.0";
 
 $PROTOCOL = stripos($_SERVER['SERVER_PROTOCOL'], 'https') ? 'https://' : 'http://';
@@ -58,12 +58,15 @@ $AUTHENTICATION_METHODS = array(
         'secret' => "***REMOVED***",
         'version' => "v2.2",
         'callback' => $BASE_PATH . "/auth/oauth2/facebook_token.php"),
-    'LinkedIn' => array(),
+    'LinkedIn' => array(
+        'id' => '***REMOVED***',
+        'secret' => '***REMOVED***',
+        'callback' => $BASE_PATH . '/auth/oauth2/linkedin_token.php'
+    ),
     'X.509' => array(),
 );
 
 $GROUPER = array(
-    //'serviceBaseURL' => 'https://sso.ia2.inaf.it/grouper-ws/servicesRest',
     'wsdlURL' => 'http://localhost:8087/grouper-ws/services/GrouperService_v2_3?wsdl',
     'user' => 'GrouperSystem',
     'password' => '***REMOVED***'
diff --git a/include/front-controller.php b/include/front-controller.php
index f1996e20b225a4733acf0a7693ede42d2ed0d450..2c94ee4e4c1fe1b3161d1109bc6344933830747e 100644
--- a/include/front-controller.php
+++ b/include/front-controller.php
@@ -12,14 +12,20 @@ function setCallback() {
 
     $callback = Flight::request()->data['callback'];
     $session->setCallbackURL($callbackHandler, isset($callback) ? $callback : null);
+    return $session->getCallbackURL();
 }
 
 Flight::route('/', function() {
     startSession();
-    setCallback();
-    global $session, $AUTHENTICATION_METHODS;
-    Flight::render('index.php', array('title' => 'RAP',
-        'session' => $session, 'auth' => $AUTHENTICATION_METHODS));
+    $callback = setCallback();
+    global $session, $callbackHandler, $AUTHENTICATION_METHODS;
+    if ($callback !== null && $session->user !== null) {
+        $redirectURL = $callbackHandler->getLoginWithTokenURL($session->user->id, $callback);
+        Flight::redirect($redirectURL);
+    } else {
+        Flight::render('index.php', array('title' => 'RAP',
+            'session' => $session, 'auth' => $AUTHENTICATION_METHODS));
+    }
 });
 
 Flight::route('GET /logout', function() {
@@ -38,6 +44,11 @@ Flight::route('/facebook', function() {
     Flight::redirect('/auth/oauth2/facebook_login.php');
 });
 
+Flight::route('/linkedIn', function() {
+    startSession();
+    Flight::redirect('/auth/oauth2/linkedin_login.php');
+});
+
 Flight::route('/eduGAIN', function() {
     startSession();
     Flight::redirect('/auth/saml2/aai.php');
diff --git a/include/gui-backend.php b/include/gui-backend.php
index 0d1b28115e83564a3292c5c89f05cd3e2d5238bc..d29b905fe532b7216657c4a4c9e8b60dbffc7eae 100644
--- a/include/gui-backend.php
+++ b/include/gui-backend.php
@@ -35,7 +35,7 @@ Flight::route('GET /user', function() {
 Flight::route('POST /join', function() {
 
     checkSession();
-    global $session, $dao;
+    global $session, $dao, $mailSender;
 
     $selectedUserIndex = Flight::request()->data['selectedUserIndex'];
     $selectedSearchResult = $session->userSearchResults[$selectedUserIndex];
@@ -43,7 +43,7 @@ Flight::route('POST /join', function() {
 
     $token = RAP\Util::createNewToken();
     $dao->createJoinRequest($token, $session->user->id, $targetUserId);
-    RAP\MailSender::sendJoinEmail($selectedSearchResult->getUser(), $session->user);
+    $mailSender->sendJoinEmail($selectedSearchResult->getUser(), $session->user, $token);
 
     echo $selectedSearchResult->userDisplayText;
 });
diff --git a/include/init.php b/include/init.php
index dccbb58e6b4d7750e32089660ae98e95ba800940..28f658751c58c669460ffbc4ce90b422132fe4dc 100644
--- a/include/init.php
+++ b/include/init.php
@@ -55,6 +55,7 @@ switch ($DATABASE['dbtype']) {
 
 $callbackHandler = new RAP\CallbackHandler($dao, $BASE_PATH, $CALLBACKS);
 $userHandler = new RAP\UserHandler($dao, $GROUPER);
+$mailSender = new RAP\MailSender($_SERVER['HTTP_HOST'], $BASE_PATH);
 
 function startSession() {
     session_start();
diff --git a/include/rest-web-service.php b/include/rest-web-service.php
index 27aca4c81db10b482bead590619a21964df4c798..64d5880d2b2d67361de87fadbf968a65ce67c532 100644
--- a/include/rest-web-service.php
+++ b/include/rest-web-service.php
@@ -41,11 +41,47 @@ Flight::route('GET ' . $WS_PREFIX . '/user/@userId', function($userId) {
 Flight::route('GET ' . $WS_PREFIX . '/user', function() {
 
     global $dao;
-    
+
     $searchText = Flight::request()->query['search'];
     $users = $dao->searchUser($searchText);
     echo json_encode($users);
 });
 
+/**
+ * Create new user from identity data. Return the new user.
+ */
+Flight::route('POST ' . $WS_PREFIX . '/user', function() {
+
+    global $userHandler;
+
+    $postData = Flight::request()->data;
+
+    $user = new RAP\User();
+
+    $identity = new RAP\Identity($postData['type']);
+
+    $identity->email = $postData['email'];
+    $identity->typedId = $postData['typedId'];
+    if (isset($postData['name'])) {
+        $identity->name = $postData['name'];
+    }
+    if (isset($postData['surname'])) {
+        $identity->surname = $postData['surname'];
+    }
+    if (isset($postData['institution'])) {
+        $identity->institution = $postData['institution'];
+    }
+    if (isset($postData['eppn'])) {
+        $identity->eppn = $postData['eppn'];
+    }
+
+    $user->addIdentity($identity);
+
+    $userHandler->saveUser($user);
+
+    echo json_encode($user);
+});
+
 Flight::route('GET ' . $WS_PREFIX . '/test', function() {
+    
 });
diff --git a/include/user-data.php b/include/user-data.php
index 92219036c1c580bd6daa1160edd0bbce14a5a806..ca50bf0e63b38751f079e870ad25ee260b2382f6 100644
--- a/include/user-data.php
+++ b/include/user-data.php
@@ -1,16 +1,18 @@
 <?php foreach ($user->identities as $identity) { ?>
     <dl class="dl-horizontal">
         <dt>
-            <?php if ($identity->primary) { ?>
-                <span class="primary-identity-icon" data-toggle="tooltip" data-placement="left" title="This is your primary identity. You will receive email messages on the address related to this identity.">
-                    <span class="glyphicon glyphicon-star"></span>
-                </span>
-            <?php } else { ?>
-                <span class="primary-identity-icon" data-toggle="tooltip" data-placement="left" title="Click on this icon to set this as the primary identity">
-                    <a href="#" onclick="setPrimaryIdentity(<?php echo $identity->id; ?>);">
-                        <span class="glyphicon glyphicon-star-empty"></span>
-                    </a>
-                </span>
+            <?php if (!isset($readonly)) { ?>
+                <?php if ($identity->primary) { ?>
+                    <span class="primary-identity-icon" data-toggle="tooltip" data-placement="left" title="This is your primary identity. You will receive email messages on the address related to this identity.">
+                        <span class="glyphicon glyphicon-star"></span>
+                    </span>
+                <?php } else { ?>
+                    <span class="primary-identity-icon" data-toggle="tooltip" data-placement="left" title="Click on this icon to set this as the primary identity">
+                        <a href="#" onclick="setPrimaryIdentity(<?php echo $identity->id; ?>);">
+                            <span class="glyphicon glyphicon-star-empty"></span>
+                        </a>
+                    </span>
+                <?php } ?>
             <?php } ?>
             Type
         </dt>
@@ -18,7 +20,7 @@
         <dt>E-mail</dt>
         <dd><?php echo $identity->email; ?></dd>
         <?php if ($identity->eppn !== null) { ?>
-            <dt>EduPersonPrincipalName</dt>
+            <dt><abbr title="EduPerson Principal Name, an unique identifier used into federations.">EPPN</abbr></dt>
             <dd><?php echo $identity->eppn; ?></dd>
         <?php } ?>
         <?php if ($identity->name !== null) { ?>
diff --git a/js/index.js b/js/index.js
index 7ca705780f22db0aa06120a4857979892095b30f..a55d573df947a3c6262da841bc5a46520f3e69fb 100644
--- a/js/index.js
+++ b/js/index.js
@@ -63,6 +63,9 @@
         // Add click event handler to join request button
         $(document).on('click', '#send-join-request-btn', sendJoinRequest);
 
+        // Add event handler for closing the info alert message.
+        // This is used instead of data-dismiss="alert" in order to maintain
+        // the alert inside the DOM and be able to show it again if necessary.
         $(document).on('click', '#info-message-alert .close', function () {
             $('#info-message-alert').addClass('hide');
         });
diff --git a/views/confirm-join.php b/views/confirm-join.php
index aafd5fda9fdbd7f54655209097d6796ffdb2f783..15372a68b3f3a44521ad776bd127cde16db60a2a 100644
--- a/views/confirm-join.php
+++ b/views/confirm-join.php
@@ -12,6 +12,7 @@ include 'include/header.php';
                 <div class="panel-body">
                     <?php
                     $user = $applicantUser;
+                    $readOnly = true;
                     include 'include/user-data.php';
                     ?>        
                 </div>
@@ -25,6 +26,7 @@ include 'include/header.php';
                 <div class="panel-body">
                     <?php
                     $user = $targetUser;
+                    $readOnly = true;
                     include 'include/user-data.php';
                     ?>        
                 </div>