diff --git a/.gitignore b/.gitignore
index f70f2e112eb1ef6f2f6d9ac0bc3ad73689ff02d6..5d63beec995d5919657b6e8e5b4466fbabd4a318 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,4 @@ logs/
 vendor/
 client-icons/
 /nbproject/
+*.pem
diff --git a/classes/CallbackHandler.php b/classes/CallbackHandler.php
index dfd6c5d935ea772fb6bbe867ec4ad96615fbf7a4..5c5fbbee0b5b804c8d222ec6d8bbee9c299d7896 100644
--- a/classes/CallbackHandler.php
+++ b/classes/CallbackHandler.php
@@ -29,14 +29,10 @@ namespace RAP;
  */
 class CallbackHandler {
 
-    private $dao;
-    private $basePath;
-    private $callbacks;
-
-    public function __construct(DAO $dao, $basePath, $callbacks) {
-        $this->dao = $dao;
-        $this->basePath = $basePath;
-        $this->callbacks = $callbacks;
+    private $locator;
+
+    public function __construct(Locator $locator) {
+        $this->locator = $locator;
     }
 
     /**
@@ -91,8 +87,17 @@ class CallbackHandler {
         return null;
     }
 
-    public function manageLoginRedirect($user, SessionData $session) {
-
+    public function manageLoginRedirect(User $user, SessionData $session) {
+        
+        if($session->getOAuth2Data() !== null) {
+            $session->user = $user;
+            $session->save();
+            $redirectUrl = $this->locator->getOAuth2RequestHandler()->getCodeResponseUrl();
+            $session->setOAuth2Data(null);
+            header('Location: ' . $redirectUrl);
+            die();
+        }
+        
         if ($session->getCallbackURL() === null) {
             http_response_code(401);
             die("Unauthorized callback URL");
diff --git a/classes/DAO.php b/classes/DAO.php
index 51b48f141ff15a6649111fc1ad135862b81adc41..54bc5ff6204aae74e7dbb8e9b0176846766b7a26 100644
--- a/classes/DAO.php
+++ b/classes/DAO.php
@@ -40,22 +40,20 @@ interface DAO {
      * @param type $token login token
      * @param type $userId
      */
-    function createLoginToken($token, $userId);
+    function createAccessToken(string $token, string $code, string $userId): string;
 
     /**
-     * Retrieve the user ID from the login token.
-     * @param type $token
-     * @return type user ID
+     * Retrieve the access token value from the code.
      */
-    function findLoginToken($token);
+    function findAccessToken(string $code): ?string;
 
     /**
-     * Delete a login token from the database. This happens when the caller
+     * Delete an access token from the database. This happens when the caller
      * application has received the token and used it for retrieving user
      * information from the token using the RAP REST web service.
      * @param type $token login token
      */
-    function deleteLoginToken($token);
+    function deleteAccessToken(string $token): void;
 
     /**
      * Create a new identity.
@@ -130,9 +128,9 @@ interface DAO {
      */
     function getOAuth2Clients();
 
-    function createOAuth2Client($client) : OAuth2Client;
+    function createOAuth2Client($client): OAuth2Client;
 
-    function updateOAuth2Client($client) : OAuth2Client;
+    function updateOAuth2Client($client): OAuth2Client;
 
     function deleteOAuth2Client($clientId);
 
@@ -140,5 +138,5 @@ interface DAO {
      * Retrieve the client from the configured client id (the one associated to
      * the secret, not the database id).
      */
-    function getOAuth2ClientByClientId($clientId) : ?OAuth2Client;
+    function getOAuth2ClientByClientId($clientId): ?OAuth2Client;
 }
diff --git a/classes/JWKSHandler.php b/classes/JWKSHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..eb49951642fa183fb97563e663f1e88d2a0ce307
--- /dev/null
+++ b/classes/JWKSHandler.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace RAP;
+
+/**
+ * Manages the JWT Key Sets.
+ */
+class JWKSHandler {
+
+    public function generateKeyPair() {
+        
+    }
+
+}
diff --git a/classes/Locator.php b/classes/Locator.php
index d4a6883e3e61bb152667a240eb8c403c0b8229dd..bee9b493b244e1b0a27626ab47d59040685db9c5 100644
--- a/classes/Locator.php
+++ b/classes/Locator.php
@@ -39,7 +39,7 @@ class Locator {
     }
 
     public function getCallbackHandler(): CallbackHandler {
-        return new \RAP\CallbackHandler($dao, $this->getBasePath());
+        return new \RAP\CallbackHandler($this);
     }
 
     public function getUserHandler(): UserHandler {
@@ -50,6 +50,10 @@ class Locator {
         return new \RAP\MailSender($_SERVER['HTTP_HOST'], $this->getBasePath());
     }
 
+    public function getOAuth2RequestHandler(): OAuth2RequestHandler {
+        return new \RAP\OAuth2RequestHandler($this);
+    }
+
     /**
      * Retrieve the SessionData object from the $_SESSION PHP variable. Create a
      * new one if it is necessary.
@@ -64,11 +68,11 @@ class Locator {
         return $this->session;
     }
 
-    public function getServiceLogger() {
+    public function getServiceLogger(): \Monolog\Logger {
         return $this->serviceLogger;
     }
 
-    public function getAuditLogger() {
+    public function getAuditLogger(): \Monolog\Logger {
         return $this->auditLogger;
     }
 
diff --git a/classes/MySQLDAO.php b/classes/MySQLDAO.php
index 6033582830ae93c5bbeea71f95ef0ca05e926c38..7dc0fd5c911d8d4f8d54280727273538bf378f17 100644
--- a/classes/MySQLDAO.php
+++ b/classes/MySQLDAO.php
@@ -45,13 +45,14 @@ class MySQLDAO implements DAO {
         return $dbh;
     }
 
-    public function createLoginToken($token, $userId) {
+    public function createAccessToken(string $token, string $code, string $userId): string {
 
         $dbh = $this->getDBHandler();
-        $stmt = $dbh->prepare("INSERT INTO login_token (token, user_id) VALUES(:token, :user_id)");
+        $stmt = $dbh->prepare("INSERT INTO access_token (token, code, user_id) VALUES(:token, :code, :user_id)");
 
         $params = array(
             ':token' => $token,
+            ':code' => $code,
             ':user_id' => $userId
         );
 
@@ -63,27 +64,27 @@ class MySQLDAO implements DAO {
         }
     }
 
-    public function findLoginToken($token) {
+    public function findAccessToken(string $code): ?string {
 
         $dbh = $this->getDBHandler();
 
-        $stmt = $dbh->prepare("SELECT user_id FROM login_token WHERE token = :token AND CURRENT_TIMESTAMP < TIMESTAMPADD(MINUTE,1,creation_time)");
-        $stmt->bindParam(':token', $token);
+        $stmt = $dbh->prepare("SELECT token FROM access_token WHERE code = :code");// AND CURRENT_TIMESTAMP < TIMESTAMPADD(MINUTE,1,creation_time)");
+        $stmt->bindParam(':code', $code);
 
         $stmt->execute();
 
         foreach ($stmt->fetchAll() as $row) {
-            return $row['user_id'];
+            return $row['token'];
         }
 
         return null;
     }
 
-    public function deleteLoginToken($token) {
+    public function deleteAccessToken($token): void {
 
         $dbh = $this->getDBHandler();
 
-        $stmt = $dbh->prepare("DELETE FROM login_token WHERE token = :token");
+        $stmt = $dbh->prepare("DELETE FROM access_token WHERE token = :token");
         $stmt->bindParam(':token', $token);
         $stmt->execute();
     }
@@ -373,7 +374,7 @@ class MySQLDAO implements DAO {
         return $clients;
     }
 
-    function createOAuth2Client($client) : OAuth2Client {
+    function createOAuth2Client($client): OAuth2Client {
         $dbh = $this->getDBHandler();
 
         try {
@@ -412,7 +413,7 @@ class MySQLDAO implements DAO {
         return $client;
     }
 
-    function updateOAuth2Client($client) : OAuth2Client {
+    function updateOAuth2Client($client): OAuth2Client {
         $dbh = $this->getDBHandler();
 
         try {
@@ -478,7 +479,7 @@ class MySQLDAO implements DAO {
         }
     }
 
-    function getOAuth2ClientByClientId($clientId) : ?OAuth2Client {
+    function getOAuth2ClientByClientId($clientId): ?OAuth2Client {
         $dbh = $this->getDBHandler();
 
         // Load clients info
diff --git a/classes/OAuth2RequestHandler.php b/classes/OAuth2RequestHandler.php
index e947368bf53c0ede8360ca2c69371ee262a7ac2c..de070dbd6477a04e329dc11f7e7dac2af4810a5e 100644
--- a/classes/OAuth2RequestHandler.php
+++ b/classes/OAuth2RequestHandler.php
@@ -61,4 +61,82 @@ class OAuth2RequestHandler {
         $session->setOAuth2Data($oauth2Data);
     }
 
+    public function getCodeResponseUrl(): string {
+
+        $session = $this->locator->getSession();
+
+        $code = base64_encode(bin2hex(openssl_random_pseudo_bytes(64)));
+        $accessToken = base64_encode(bin2hex(openssl_random_pseudo_bytes(128)));
+        $state = $session->getOAuth2Data()->state;
+
+        $userId = $session->user->id;
+
+        $this->locator->getDAO()->createAccessToken($accessToken, $code, $userId);
+
+        $redirectUrl = $session->getOAuth2Data()->redirectUrl
+                . '?code=' . $code . '&scope=profile&state=' . $state;
+
+        return $redirectUrl;
+    }
+
+    public function handleAccessTokenRequest(): array {
+
+        $this->validateAccessTokenRequest();
+
+        $code = filter_input(INPUT_POST, 'code', FILTER_SANITIZE_STRING);
+        $accessToken = $this->locator->getDAO()->findAccessToken($code);
+
+        $this->validateParametersMatching();
+
+        $token = [];
+        $token['access_token'] = $accessToken;
+        $token['token_type'] = 'bearer';
+        $token['expires_in'] = 300;
+
+        return $token;
+    }
+
+    private function validateAccessTokenRequest() {
+
+        if (!isset($_POST['grant_type'])) {
+            throw new BadRequestException("Client id is required");
+        } else if ($_POST['grant_type'] !== 'authorization_code') {
+            throw new BadRequestException("grant_type must be authorization_code");
+        }
+
+        if (!isset($_POST['code'])) {
+            throw new BadRequestException("Client id is required");
+        }
+
+        if (!isset($_POST['redirect_uri'])) {
+            throw new BadRequestException("Redirect URI is required");
+        }
+        
+        // Note: theorically the standard wants also the client_id here,
+        // however some clients don't send it
+    }
+
+    private function validateParametersMatching() {
+        
+    }
+
+    public function handleCheckTokenRequest(): array {
+
+        if (!isset($_POST['token'])) {
+            throw new BadRequestException("Access token id is required");
+        }
+
+        $accessToken = filter_input(INPUT_POST, 'token', FILTER_SANITIZE_STRING);
+
+        //if($accessToken)
+
+        $result = [];
+        $result['exp'] = 3600;
+        $result['user_name'] = "test";
+        $result['client_id'] = "gms";
+        $result['scope'] = "profile";
+
+        return $result;
+    }
+
 }
diff --git a/classes/SessionData.php b/classes/SessionData.php
index 878459407f3ab67fbde751dcd905b8c7c919cba9..ee0e0bc000ce46c56e58dafbecc0d28c37a4871b 100644
--- a/classes/SessionData.php
+++ b/classes/SessionData.php
@@ -60,7 +60,7 @@ class SessionData {
         }
     }
 
-    public function setOAuth2Data(OAuth2Data $oauth2Data) {
+    public function setOAuth2Data(?OAuth2Data $oauth2Data) {
         $this->oauth2Data = $oauth2Data;
         $this->save();
     }
diff --git a/classes/UserHandler.php b/classes/UserHandler.php
index 4614a8f17b5004b7552c16e2a60eda7d56f8a76b..ec0002910d5c447a683dd73558934ab1d9f03003 100644
--- a/classes/UserHandler.php
+++ b/classes/UserHandler.php
@@ -30,11 +30,9 @@ namespace RAP;
 class UserHandler {
 
     private $dao;
-    private $grouperConfig;
 
-    public function __construct(DAO $dao, $grouperConfig) {
+    public function __construct(DAO $dao) {
         $this->dao = $dao;
-        $this->grouperConfig = $grouperConfig;
     }
 
     /**
diff --git a/classes/social/GoogleLogin.php b/classes/social/GoogleLogin.php
new file mode 100644
index 0000000000000000000000000000000000000000..f2174d092a40fef77e5648783b5032d7d218feaa
--- /dev/null
+++ b/classes/social/GoogleLogin.php
@@ -0,0 +1,97 @@
+<?php
+
+namespace RAP;
+
+class GoogleLogin {
+
+    protected $locator;
+
+    public function __construct(Locator $locator) {
+        $this->locator = $locator;
+    }
+
+    public function call() {
+        // Retrieve Google configuration
+
+        $Google = $this->locator->config->authenticationMethods->Google;
+
+        $client = new \Google_Client(array(
+            'client_id' => $Google->id,
+            'client_secret' => $Google->secret,
+            'redirect_uri' => $this->locator->getBasePath() . $Google->callback,
+        ));
+
+        // Ask permission to obtain user email and profile information
+        $client->setScopes(array(\Google_Service_People::USERINFO_EMAIL, \Google_Service_People::USERINFO_PROFILE));
+
+        if (isset($_REQUEST['logout'])) {
+            // Reset the access token stored into the session
+            unset($_SESSION['access_token']);
+        }
+
+        if (isset($_GET['code'])) {
+            // An access token has been returned from the auth URL.
+            $client->authenticate($_GET['code']);
+            $_SESSION['access_token'] = $client->getAccessToken();
+        }
+
+        if ($client->getAccessToken()) {
+
+            // Query web service for retrieving user information
+            $service = new \Google_Service_People($client);
+
+            try {
+                $res = $service->people->get('people/me', array('requestMask.includeField' => 'person.names,person.email_addresses'));
+            } catch (Google_Service_Exception $e) {
+                echo '<p>' . json_encode($e->getErrors()) . '</p>';
+                $thisPage = $PROTOCOL . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'];
+                echo '<p><a href="' . $thisPage . '?logout">Click here to unset the access token</a></p>';
+            }
+
+            $name = $res->getNames()[0]->getGivenName();
+            $surname = $res->getNames()[0]->getFamilyName();
+
+            $emailAddresses = [];
+            foreach ($res->getEmailAddresses() as $addr) {
+                array_push($emailAddresses, $addr->value);
+            }
+
+            $typedId = explode('/', $res->getResourceName())[1];
+
+            // Search if the user is already registered into RAP using the Google ID.
+            $user = $this->locator->getUserHandler()->findUserByIdentity(Identity::GOOGLE, $typedId);
+
+            $session = $this->locator->getSession();
+
+            if ($user === null) {
+                // Create new user
+                $user = new \RAP\User();
+
+                $identity = new Identity(Identity::GOOGLE);
+                $identity->email = $emailAddresses[0];
+                $identity->name = $name;
+                $identity->surname = $surname;
+                $identity->typedId = $typedId;
+
+                $user->addIdentity($identity);
+
+                $session->userToLogin = $user;
+                $session->save();
+
+                header('Location: ' . $this->locator->getBasePath() . '/tou-check');
+                die();
+            }
+
+            $this->locator->getAuditLogger()->info("LOGIN,Google," . $user->id);            
+            $this->locator->getCallbackHandler()->manageLoginRedirect($user, $session);
+
+            die();
+        } else {
+            // Redirect to Google authorization URL for obtaining an access token
+            $authUrl = $client->createAuthUrl();
+            header('Location: ' . $authUrl);
+            die();
+        }
+    }
+
+}
diff --git a/config-example.json b/config-example.json
index 7cf6ab4b58b1124f84a479c8314d5e30c08fc54c..f73a89ee9e4d51dc28b64c5226584609c11c6e9d 100644
--- a/config-example.json
+++ b/config-example.json
@@ -17,7 +17,7 @@
         "Google": {
             "id": "XXXXXX",
             "secret": "XXXXXX",
-            "callback": "/auth/social/google_token.php"
+            "callback": "/auth/social/google"
         },
         "Facebook": {
             "id": "XXXXXX",
diff --git a/include/front-controller.php b/include/front-controller.php
index 0c2d79e14032daf4e1e57788cee28ac47fb95df9..8ab6e953554a3b273ac026101eab13b78409ca9a 100644
--- a/include/front-controller.php
+++ b/include/front-controller.php
@@ -22,7 +22,7 @@ function setCallback($callback) {
  * services list if a valid callback is not found
  */
 Flight::route('/', function() {
-    
+
     session_start();
     global $locator;
 
@@ -36,6 +36,7 @@ Flight::route('/', function() {
             renderMainPage($authPageModel);
             break;
         default:
+            session_destroy();
             Flight::render('services-list.php', array('title' => 'RAP',
                 'version' => $locator->getVersion(),
                 'action' => $locator->getBasePath() . '/'));
@@ -49,7 +50,7 @@ function renderMainPage(RAP\AuthPageModel $authPageModel) {
         'version' => $locator->getVersion(), 'model' => $authPageModel));
 }
 
-Flight::route('/oauth2/authorize', function() {
+Flight::route('GET /auth/oauth2/authorize', function() {
 
     session_start();
     global $locator;
@@ -60,10 +61,34 @@ Flight::route('/oauth2/authorize', function() {
     Flight::redirect('/?action=oaut2client');
 });
 
-Flight::route('GET /admin', function() {
+Flight::route('POST /auth/oauth2/token', function() {
 
-    session_start();
     global $locator;
+
+    $requestHandler = new \RAP\OAuth2RequestHandler($locator);
+    $token = $requestHandler->handleAccessTokenRequest();
+
+    Flight::json($token);
+});
+
+Flight::route('POST /auth/oauth2/check_token', function() {
+    
+    global $locator;
+
+    $requestHandler = new \RAP\OAuth2RequestHandler($locator);
+    $token = $requestHandler->handleCheckTokenRequest();
+
+    Flight::json($token);
+});
+
+Flight::route('GET /auth/oidc/jwks', function() {
+    
+    global $locator;
+
+    $requestHandler = new \RAP\OAuth2RequestHandler($locator);
+    $token = $requestHandler->handleCheckTokenRequest();
+
+    Flight::json($token);
 });
 
 Flight::route('GET /logout', function() {
@@ -80,8 +105,11 @@ function sendAuthRedirect($url) {
     Flight::redirect($url);
 }
 
-Flight::route('/google', function() {
-    sendAuthRedirect('/auth/social/google_token.php');
+Flight::route('/auth/social/google', function() {
+    session_start();
+    global $locator;
+    $googleLogin = new \RAP\GoogleLogin($locator);
+    $googleLogin->call();
 });
 
 Flight::route('/facebook', function() {
diff --git a/include/init.php b/include/init.php
index b366300b9c2c1283b3de92a6ebbfae1eab3ea0ff..ca98a295f8055497aca32ec23799ff15ee54a2e1 100644
--- a/include/init.php
+++ b/include/init.php
@@ -28,12 +28,21 @@
 define('ROOT', dirname(dirname(__FILE__)));
 
 // Defining autoload for RAP classes
-spl_autoload_register(function ($class_name) {
+spl_autoload_register(function ($class) {
     $prefix = "RAP\\";
     $len = strlen($prefix);
-    if (strncmp($prefix, $class_name, $len) === 0) {
-        $classpath = ROOT . '/classes/' . substr($class_name, $len, strlen($class_name) - $len) . '.php';
-        require $classpath;
+
+    if (strncmp($prefix, $class, $len) === 0) {
+
+        $fileName = substr($class, $len, strlen($class) - $len);
+
+        $classDirectories = ['/', '/social/', '/exceptions/'];
+        foreach ($classDirectories as $directory) {
+            $classpath = ROOT . '/classes' . $directory . $fileName . '.php';
+            if (file_exists($classpath)) {
+                require_once $classpath;
+            }
+        }
     }
 });
 
diff --git a/index.php b/index.php
index 754f719c6ba324aeb4dd06185284f4094c3d769c..777c43d366a87a7484aa56c5f065e1e1a748014b 100644
--- a/index.php
+++ b/index.php
@@ -30,3 +30,14 @@ include './include/rest-web-service.php';
 
 // Starting Flight framework
 Flight::start();
+
+// Error handling
+Flight::map('error', function(Exception $ex) {
+    if ($ex instanceof \RAP\BadRequestException) {
+        http_response_code(400);
+        echo "Bad request: " . $ex->message;
+    } else {
+        echo $ex->getTraceAsString();
+    }
+});
+
diff --git a/sql/setup-database.sql b/sql/setup-database.sql
index c4faac8a8d7fdb6cf21fc46ecb74d4757e1aff6d..11f927025053b39e7f559d800680e860ab05f59a 100644
--- a/sql/setup-database.sql
+++ b/sql/setup-database.sql
@@ -42,10 +42,11 @@ SET FOREIGN_KEY_CHECKS=0;
 ALTER TABLE `user` ADD FOREIGN KEY (`primary_identity`) REFERENCES `identity`(`id`);
 SET FOREIGN_KEY_CHECKS=1;
 
-CREATE TABLE `login_token` (
+CREATE TABLE `access_token` (
   `id` bigint(20) NOT NULL AUTO_INCREMENT,
-  `token` varchar(255) NOT NULL,
-  `user_id` text,
+  `token` text NOT NULL,
+  `user_id` text NOT NULL,
+  `code` text NOT NULL,
   `creation_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
   PRIMARY KEY (`id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
diff --git a/views/main-page.php b/views/main-page.php
index 36bc2d36b2711f5c0da4cde9c7a34c6ab994f0ce..1aa6c049ce5423e885c593ff03f9598fd9a19919 100644
--- a/views/main-page.php
+++ b/views/main-page.php
@@ -33,7 +33,7 @@ include 'include/header.php';
             <div class="home-box">
                 <div class="img-wrapper">
                     <?php if ($model->google) { ?>
-                        <a href="google" class="animated pulse">
+                        <a href="auth/social/google" class="animated pulse">
                             <img src="img/google-60.png" alt="Google Logo" />
                         </a>
                     <?php } ?>