I am beginning the cycle of creating my role based access control into my framework. I now want to log the user into my application and my _user table looks like this:
CREATE TABLE `_users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) NOT NULL,
`hash` varchar(255) NOT NULL,
`session` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
)
I create my user like so in my model, where bin2hex() is my IV / salt.
Database::getInstance()->Prepare( 'INSERT INTO _users (username, hash, session) VALUES (?, ?, ?)' )
->execute( array(
'Test',
password_hash( 'Test', PASSWORD_BCRYPT ),
md5( time() . bin2hex( random_bytes( 32 ) ) )
) );
To then log into the user, I use a method. The method I use in the request is POST because my application has built in CRSF tokens. If the CRSF token is passed, then a xauth_protected aliased class is instanced which ensures that X-Auth header exists and is of md5(session_id()) to ensure that no cross-site-origin requests can be made.
public function login( Request $request )
{
$this->middleware( 'json_response', $request );
$this->middleware( 'xauth_protected', $request );
if( !$request->has( 'username' ) || !$request->has( 'password') )
{
echo json_encode( array(
'status' => FALSE,
'reason' => 'Please fill in the required fields...',
) );
return;
}
$stmt = Database::getInstance()->Prepare( 'SELECT id, hash, session FROM _users WHERE username = ? LIMIT 1' );
$stmt->execute( array( $request->username ) );
$row = (object) $stmt->fetch();
if( isset( $row->id ) )
{
if( password_verify( $request->password, $row->hash ) )
{
// $_SESSION['oath'] = $row->session
// This is used to find the user that is logged in, if it is set
$request->setSession( 'oauth', $row->session );
echo json_encode( array(
'status' => TRUE,
) );
return;
}
echo json_encode( array(
'status' => FALSE,
'reason' => 'Invalid credentials...',
) );
return;
}
echo json_encode( array(
'status' => FALSE,
'reason' => 'Sorry, that username was not found in our records...',
) );
}
To then test this method, I can use:
const formData = new FormData();
formData.append( 'crsf_token', sessionStorage.getItem( 'token' ) ); // CRSF token is set elsewhere
formData.append( 'username', document.getElementById( 'username' ).value ); // Example input field
formData.append( 'password', document.getElementById( 'password' ).value ); // Example input field
fetch( App.__viewFactory.homepage.login, { // This is a prestored route that looks like /oauth/login
method: 'POST',
headers: {
'X-Auth': sessionStorage.getItem( 'session' ) // md5( session_id() ) which is set elsewhere
},
body: formData
} )
.then( response => response.json() )
.then( data => {
if( data.result ) {
window.location.href = App.__viewFactory.dashboard.view; // prestored route to the dashboard
return;
}
document.getElementById( 'login-error' ).innerHTML = data.reason;
} )
.catch(error => {
document.getElementById( 'login-error' ).innerHTML = 'Oh no! Something went wrong.';
});
I have incorporated CRSF protected, custom headers using the session_id() to prevent cross-origin requests (since my framework rewrites all requests to my index page, this then turns on CORS). Is everything I am doing on the database side as secure? Is there something I could be doing better?
md5(session_id())- it probably has absolutely no adversary effect but deception is a key defence principle even if so. These are set on a.tplfile which my page executes. I can show that if needed @YourCommonSense \$\endgroup\$