If you ever had a bank account you are familiar with TAN-s (Transaction Authentication Number). What we will do today, is create user login using that kind of system. This will be just simple overview how to do it so you can create more complex and secure login systems. You need to have basic knowledge about classes, HTML, CSS and AJAX because I will not explain in depth what I do.
What is TAN?Top
TAN stands for Transaction Authentication Number. It is used to authenticate transactions on Net-banking. Some banks use it when user want to create transaction and some banks use it when user tries to log in. TAN tables are tables that have string (mostly 6-8 characters long). Each string has its own row and columns. Using that grid it’s identified and asked for user to enter it.
Set upTop
First create folders and files as you can see them on picture below this text. You can download jQuery library from here and place it in jquery folder and loader image you can download from here.
After that create new database execute SQL code below in that database (or in any existing one).
-- `tan_tables` table CREATE TABLE IF NOT EXISTS `tan_tables` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL, `row` int(11) NOT NULL, `column` int(11) NOT NULL, `value` varchar(32) COLLATE utf8_bin NOT NULL, PRIMARY KEY (`id`), KEY `user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin ; -- `users` table CREATE TABLE IF NOT EXISTS `users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(25) COLLATE utf8_bin NOT NULL, `password` varchar(32) COLLATE utf8_bin NOT NULL, `salt` varchar(12) COLLATE utf8_bin NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin ; -- Relations ALTER TABLE `tan_tables` ADD CONSTRAINT `tan_tables_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
Now, that we have folders, files and database we can continue.
Markup and CSSTop
Now we do some HTML code. This will be our index.php.
< ?php
/**
* @author Marijan Šuflaj <msufflaj32@gmail.com>
* @link http://www.php4every1.com
*/
session_start();
$row = rand(1, 6);
$column = rand(1, 4);
$_SESSION['tan'] = array(
'row' => $row,
'column' => $column
);
?>
< !DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Login using tan tables.</title>
<link href="css/main.css" type="text/css" media="screen, projection" rel="stylesheet" />
</head>
<body>
<div id="wrapper">
<div id="message" style="display: none;">
</div>
<div id="waiting" style="display: none;">
Please wait<br />
<img src="images/ajax-loader.gif" title="Loader" alt="Loader" />
</div>
<form method="post" id="loginForm">
<fieldset>
<legend>Login form</legend>
<span style="font-size: 0.9em;">Please insert your credidentials.</span>
<p>
<label for="email">E-Mail:</label>
<input type="text" name="email" class="inputText" id="email" value="" />
</p>
<p>
<label for="password">Password:</label>
<input type="password" name="password" class="inputText" id="password" value="" />
</p>
<p>
<label for="tan">
Tan code (row: <span id="tanRow">
< ?php echo $row; ?>
</span>, column: <span id="tanColumn">
< ?php echo $column; ?>
</span>):
</label>
<input type="tan" name="tan" class="inputText" id="tan" value="" />
</p>
<p>
<input type="submit" name="submit" id="submit" style="float: right; clear: both; margin-right: 3px;" value="Submit" />
</p>
</fieldset>
</form>
</div>
<script type="text/javascript" src="js/jquery/jquery-1.3.2.js"></script>
<script type="text/javascript" src="js/userLogin.js"></script>
</body>
</html>
This is all just basic HTML. At top we have some PHP code that takes care about creating session and choosing grid position for TAN code. $row can be any number from 1 to 6 because we’ll have 6 rows. $column can be any number from 1 to 4 because we’ll have 4 columns. After that we add those values to tan array in our $_SESSION. Lines 48 and 50 just echo those values so user knows what to enter.
Copy this code to your CSS file.
@CHARSET "UTF-8";
body {
background-color: #f0f0f0;
}
#wrapper {
margin: 100px auto;
width: 310px;
}
#waiting {
color: #767676;
text-align: center;
}
fieldset {
margin-top: 10px;
background: #fff;
border: 1px solid #c8c8c8;
background-color: #fff;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
}
legend {
background-color: #fff;
border-top: 1px solid #c8c8c8;
border-right: 1px solid #c8c8c8;
border-left: 1px solid #c8c8c8;
font-size: 1.2em;
padding: 0px 7px;
-moz-border-radius-topleft: 5px;
-webkit-border-radius-topleft: 5px;
-moz-border-radius-topright: 5px;
-webkit-border-radius-topright: 5px;
}
.inputText {
width: 275px;
}
.success {
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
width: 298px;
background: #a5e283;
border: #337f09 1px solid;
padding: 5px;
}
.error {
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
width: 298px;
background: #ea7e7e;
border: #a71010 1px solid;
padding: 5px;
}
Config and database connectionTop
Now we will create our config.php that will have configuration values and then we will create some simple database class that we will use to connect to our database and execute queries.
Our config.php looks like this.
< ?php
/**
* @author Marijan Šuflaj <msufflaj32@gmail.com>
* @link http://www.php4every1.com
*/
/**
* Config class.
*/
class config
{
/**
* Database host.
*/
const HOST = 'localhost';
/**
* Database username.
*/
const USERNAME = 'root';
/**
* Database password.
*/
const PASSWORD = '';
/**
* Database name.
*/
const NAME = 'tan_table_tutorial';
}
As you can see it’s really nothing special. It’s just one class that has some constants defined. We use class so we can easily access our config information in any function or class so we do not need some global variables.
Now we will create our database.php.
< ?php
/**
* @author Marijan Šuflaj <msufflaj32@gmail.com>
* @link http://www.php4every1.com
*/
/**
* Database class.
*
*/
class dataBase {
/**
* Connection resource.
*
* @var resource
*/
private static $_con = null;
/**
* dataBase class.
*
* @var dataBase
*/
private static $_self = null;
/**
* Constructor.
*
*/
private function __construct()
{
}
/**
* Destructor.
*/
public function __destruct()
{
mysql_close(self::$_con);
}
/**
* Returns instance of this class.
*
* @return dataBase
*/
public static function getInstance()
{
return (is_null(self::$_self)) ? new dataBase() : self::$_self;
}
/**
* Connect.
*
* @param string $host Host
* @param string $user User
* @param string $pass Pass
* @param string $db Database
* @return bool True if connected, otherwise false
*/
public static function connect($host, $user, $pass, $db)
{
if (is_resource(self::$_con))
return true;
if ((self::$_con = mysql_connect($host, $user, $pass)) === false)
return false;
return (mysql_select_db($db, self::$_con) === false) ? false : true;
}
/**
* Function inserts new row.
*
* @param string $table Table name
* @param array $fields Fields
* @return bool
*/
public function insert($table, $fields)
{
if (!is_array($fields))
return false;
$sql = 'INSERT INTO `' . mysql_real_escape_string($table) . '` (';
$tmp = 'VALUES (';
foreach ($fields as $field => $value) {
$sql .= '`' . mysql_real_escape_string($field) . '`, ';
$tmp .= "'" . mysql_real_escape_string($value) . "', ";
}
$sql = substr($sql, 0, -2) . ') ' . substr($tmp, 0, -2) . ')';
if (!mysql_query($sql, self::$_con))
return false;
return true;
}
/**
* Function that executes query.
*
* @param string $query
* @return resource | false
*/
public function query($query)
{
if (!($rez = mysql_query($query, self::$_con)))
return false;
return $rez;
}
}
We implement here singletron patter so that we can only have 1 connection to our database. To do that we make our __constructor() private. __destructor() just closes our MySQL connection. getInstance() is static function that returns instance of our class (if it does not exist it creates new instance). connect() is simple function that we use to connect to our database and selecting database. It returns false on error or true on success. Then we have function insert() that function is used to simplify inserting. We just pass our table name and in an array key => value pairs. Key is column name in our database and value is columns value that will be inserted. Now we have function query() that is used to execute query.
AJAX SubmitTop
Copy this to userLogin.js.
/**
* @author Marijan Šuflaj <msufflaj32 @gmail.com>
* @link http://www.php4every1.com
*/
$(document).ready(function(){
$('#submit').click(function() {
$('#waiting').show(500);
$('#loginForm').hide(0);
$('#message').hide(0);
$.ajax({
type : 'POST',
url : 'post.php',
dataType : 'json',
data : {
email : $('#email').val(),
password : $('#password').val(),
tan : $('#tan').val()
},
success : function(data){
$('#waiting').hide(500);
$('#message').removeClass().addClass((data.error === true) ? 'error' : 'success')
.text(data.msg).show(500);
if (data.error === true) {
$('#tanRow').text(data.tan.row);
$('#tanColumn').text(data.tan.column);
$('#loginForm').show(500);
}
},
error : function(XMLHttpRequest, textStatus, errorThrown) {
$('#waiting').hide(500);
$('#message').removeClass().addClass('error')
.text('There was an error.').show(500);
$('#loginForm').show(500);
}
});
return false;
});
});
This is code that handles AJAX requests. I don’t want to repeat myself so check this tutorial in order to know what’s going on. I don’t want to explain twice how this is done because I’ve done just some minor changes to my previous tutorial.
Validate Log InTop
This is code that validates users data.
< ?php
/**
* @author Marijan Šuflaj <msufflaj32@gmail.com>
* @link http://www.php4every1.com
*/
session_start();
require_once 'incs/database.php';
require_once 'config.php';
if (!dataBase::connect(
config::HOST,
config::USERNAME,
config::PASSWORD,
config::NAME
)) {
$return['error'] = true;
$return['msg'] = 'Failed to connect to database.';
}
$db = dataBase::getInstance();
while (true) {
if (empty($_POST['email'])) {
$return['error'] = true;
$return['msg'] = 'You did not enter you email.';
break;
}
if (empty($_POST['password'])) {
$return['error'] = true;
$return['msg'] = 'You did not enter you password.';
break;
}
if (empty($_POST['tan'])) {
$return['error'] = true;
$return['msg'] = 'You did not enter you tan code.';
break;
}
$sql = "SELECT `salt` "
. "FROM `users` "
. "WHERE `username` = '" . mysql_real_escape_string($_POST['email']) . "' "
. "LIMIT 1";
if (($rez = $db->query($sql)) === false) {
$return['error'] = true;
$return['msg'] = 'Failed to execute query.';
break;
}
if (mysql_num_rows($rez) !== 1) {
$return['error'] = true;
$return['msg'] = 'Username with this e-mail does not exists.';
break;
}
$salt = mysql_fetch_assoc($rez);
$pass = md5($_POST['password']);
$div = intval(32 / strlen($_POST['password']));
$pass = md5(substr($pass, 0, $div)
. $salt['salt']
. substr($pass, $div));
$sql = "SELECT `id` "
. "FROM `users` "
. "WHERE `username` = '" . mysql_real_escape_string($_POST['email']) . "' "
. " AND `password` = '" . $pass . "'"
. "LIMIT 1";
if (($rez = $db->query($sql)) === false) {
$return['error'] = true;
$return['msg'] = 'Failed to execute query.';
break;
}
if (mysql_num_rows($rez) !== 1) {
$return['error'] = true;
$return['msg'] = 'Your password is invalid.';
break;
}
$id = mysql_fetch_assoc($rez);
$sql = "SELECT * "
. "FROM `tan_tables` "
. "WHERE `row` = '" . $_SESSION['tan']['row'] . "'"
. " AND `column` = '" . $_SESSION['tan']['column'] . "'"
. " AND `value` = '" . md5($_POST['tan']) . "'"
. " AND `user_id` = '" . $id['id'] . "'";
break;
}
if (isset($return['error'])) {
$row = rand(1, 6);
$column = rand(1, 4);
$_SESSION['tan'] = array(
'row' => $row,
'column' => $column
);
$return['tan'] = array(
'row' => $row,
'column' => $column
);
}
else {
$return['error'] = false;
$return['msg'] = 'You are loged in.';
}
echo json_encode($return);
First we start session, include required files and connect to our database. Then we do some checking. If you are wondering why we use while loop to check our data, read this tutorial (at the bottom of the page). First 3 checks validate if user has entered all required data. Then we select salt from database and by doing so we check if user exists. If he does exist we check if passwords match. If that so, we check his TAN input. If that is also true then our user is ready to log in. Last we have if statement that we use to generate new TAN request (after each unsuccessful log in user needs to enter different TAN code) if user has failed to log in, or we generate message about successful log in. Then we just return our results in JSON format.
Creating new userTop
File genTansAndUser.php generates new user.
< ?php
/**
* @author Marijan Šuflaj <msufflaj32@gmail.com>
* @link http://www.php4every1.com
*/
require_once 'config.php';
require_once 'incs/database.php';
if (!dataBase::connect(
config::HOST,
config::USERNAME,
config::PASSWORD,
config::NAME
)) {
echo 'Failed to connect.';
die();
}
$db = dataBase::getInstance();
$salt = substr(md5(uniqid()), 0, 12);
$userName = 'msufflaj32@gmail.com';
$pass = 'um0b0l3sn1k';
$len = strlen($pass);
$pass = md5($pass);
$div = intval(32 / $len);
$pass = md5(substr($pass, 0, $div)
. $salt
. substr($pass, $div));
$db->insert('users', array(
'username' => $userName,
'password' => $pass,
'salt' => $salt
));
$id = mysql_insert_id();
?>
<table>
<tr>
<td> </td>
<td>Column 1</td>
<td>Column 2</td>
<td>Column 3</td>
<td>Column 4</td>
</tr>
< ?php
for ($i = 1; $i < 7; $i++) :
?>
<tr>
<td>Row < ?php echo $i; ?></td>
< ?php
for ($j = 0; $j < 5; $j++) :
if ($j === 0)
continue;
$tanVal = rand(1000000, 9999999);
$db->insert('tan_tables', array(
'user_id' => $id,
'row' => $j,
'column' => $i,
'value' => md5($tanVal),
));
?>
<td>< ?php echo $tanVal; ?></td>
< ?php
endfor;
?>
</tr>
< ?php
endfor;
?>
</table>
This is just quick example on how you can create users and it’s tans. In $userName you place users name and in $pass you place its salt. Then you just run this page and your user is generated. PHP generates tan table so you can save it somewhere else and use it later. Each TAN value is a radnom number betwean 1000000 and 9999999 that is then saved as MD5 hash.
ConclusionTop
This is very simple way to implement TAN. You can integrate it to your web site and make more secure logins for users. You can see demo here or download source code here. To log in use as username msufflaj32@gmail.com and as password um0b0l3sn1k. You can see TAN table codes below.
I hope you like it. Thank you for reading.
You mean when user logs in he get’s different TAN?
Yes, why not. You would either store that TAN code in session or in database so you can track which code you’ve sent.
Hi Marijan,
Thanks for the helpful tutorial. I was wondering. Is it possible to generate TAN codes on the spot (after authentication with user/pass perhaps) and send the TAN code by mail/sms.
I’m trying to make use of a (secure) authentication method for my website, and i’m thinking of implementing TAN by SMS.
Regards,
Oguzhan U.
osum that type website is best for learnner
i talk to u so plz give me time on gmail.com
countinue study and learn developer.
go head.
best of luck
thanks
piyush
Really like this script, not seen an example like this before.
Once a user logged in how can you allow/prevent access to particular pages and how would you log them out, I’ve tried using the session but seen to be missing something.
You can easily do it with window.location. When you get AJAX response that user is authenticated you can do window.location = “http://where.you.want.to.redirect”.
I found your advanced user login module very useful. However i want to know if there is anyway we can implement redirect to another page after successful authentication. How best can this be done and what are the codes to use. Thanks for your reply
You should have array with two dimensions for errors so you do not have
$return['msg']but$return['msg'][].Next you should remove break lines.
Then you have to, in jQery part, where you have
if (data.error === true) {a...
}
dd for loop that will echo each error.
What should I do to make the script show more than one error at a time? I guess I need to use some form of loop.
Ex: Show both “You did not enter your email.” and “You did not enter your password.” if non of them are filled in.
You can make same thing for normal page (save user to session and then check if he has privileges to access this part of the page)
.
Ok. I like your second option for the my question #1.
When i mean “protect the page” i mean like a user cannot view the content of the page without login in first.
But i think if I program it right, a true/false variable can take care of that too.
Awesome. Thanks for your help
Ya i saw that already. but typing more into that $return['msg'] string will add it to the green box that shows that u successfully logged in.
I want to add a table and have included files n everything. Sorry i wasnt more clear about it earlier.
I got 2 questions:
1) I know that in post.php it does the check to verify the user and log them in. Is there a way to do a check in the index.php file so I can display the login form if the user isnt logged in and display something else if user logged in successfully?
2) How can i protect other pages? is there a php/js if statement or something I can include to a page to protect it?
What ever you place in
if there are no errors. If you place something like
you’ll get You are loged in.
You need to explore post.php file a little bit more
.
This script is awesome but I cant figure out where to type in the encrypted data. Like when i click submit, it shows “You are logged in”. How to i put other text there?