diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000000000000000000000000000000000000..f3dbd81df688ba6f9caca5cd4af9386ea4a65b08
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,5 @@
+{
+    "require": {
+        "spomky-labs/otphp": "^10.0"
+    }
+}
diff --git a/config/config-template.php b/config/config-template.php
new file mode 100644
index 0000000000000000000000000000000000000000..5927622f63586cc83212cecf3f7f1b416a915095
--- /dev/null
+++ b/config/config-template.php
@@ -0,0 +1,4 @@
+<?php
+
+define('DB_CONFIG_LOCATION', 'location of otp_config.php');
+
diff --git a/config/config.php b/config/config.php
new file mode 100644
index 0000000000000000000000000000000000000000..5927622f63586cc83212cecf3f7f1b416a915095
--- /dev/null
+++ b/config/config.php
@@ -0,0 +1,4 @@
+<?php
+
+define('DB_CONFIG_LOCATION', 'location of otp_config.php');
+
diff --git a/config/otp_config-template.php b/config/otp_config-template.php
new file mode 100644
index 0000000000000000000000000000000000000000..685d80733fda9be864bd0463a9149b0143050354
--- /dev/null
+++ b/config/otp_config-template.php
@@ -0,0 +1,6 @@
+<?php
+define('DB_HOST','edugain-db');
+define('DB_DATABASE','edugain');
+define('USER', 'otp');
+define('PASSWORD', 'xxxx');
+
diff --git a/web/otp-server.php b/web/otp-server.php
new file mode 100644
index 0000000000000000000000000000000000000000..be9b75e5de7ebfe2d67fbf8fc9b329054e4319a9
--- /dev/null
+++ b/web/otp-server.php
@@ -0,0 +1,83 @@
+<?php
+/*
+ * The server can dwo two things - it can test if the user is defined or it can
+ * validate the otp_code against the secret in the database.
+ * 
+ * Return values:
+ *  -1 - user not found in the DB
+ *   0 - user exists but there was a missmatch in the code
+ *   1 - there was a success in verification of the code against the user secret
+ *   2 - the code has not been provided -  the user has not been verified yet
+ *   3 - the code has not been provided - just confirming that the user is verified
+ *   4 - the code has been used for a second time
+ */
+session_start();
+
+require_once('../vendor/autoload.php');
+require_once('../../config/config.php');
+require_once(DB_CONFIG_LOCATION);
+use OTPHP\TOTP;
+
+$mysqli = new mysqli(DB_HOST, USER, PASSWORD, DB_DATABASE);
+if ($mysqli->connect_error) {
+    die("Not connected");
+}
+$mysqli->set_charset('utf8');
+$mysqli->query("SET time_zone='+00:00'");
+
+if (empty($_GET['user'])) {
+    exit;
+}
+
+$user = filter_var($_GET['user'], FILTER_SANITIZE_EMAIL);
+$out = 0;
+
+$result = $mysqli->query("SELECT secret, last_code, verified from otp where user ='$user'");
+
+if ($result) {
+    if ($result->num_rows == 0) {
+        $out = -1; // the user is not defined
+    } else {
+        $r = $result->fetch_row();
+        $otpSecret = $r[0];
+        $otpLastCode = $r[1];
+        $verified = $r[2];
+        $out = 0; // the user exists in the database - this is a temporary code value
+    }
+} else {
+    exit; 
+}
+
+$otpCode = filter_var($_GET['otp'], FILTER_SANITIZE_NUMBER_INT);
+
+// check if any code has been passed and if so update the result code accordingle - again this value is temporary
+
+if ($otpCode == '' && $out == 0) {
+    if ($verified == 1) {
+        $out = 3;
+    } else {
+        $out = 2;
+    }
+}
+
+
+if ($out == 0) { // the otp code must have been provided and the user exists in the DB, the secret is taken form the DB
+    $otpObject = TOTP::create($otpSecret);
+    $otpTestCode = $otpObject->now();
+    if ($otpCode === $otpTestCode) {
+        if($otpCode === $otpLastCode) {
+            $out = 4;
+        } else {
+            $mysqli->query("UPDATE otp SET verified = 1, last_code = $otpCode where user = '$user'");
+            $out = 1;
+        }
+    } else {
+        // there was a missmatch in the codes
+        $out = 0;
+    }
+}
+header('Content-type: application/json; charset=utf-8');
+header('Access-Control-Allow-Origin: *');
+print json_encode($out, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
+
+