Im trying to put together an authentication process for user login/access control for my Angular JS boilerplate and API built in PHP.
Ive read alot about some of the various methods/techniques like JWS tokens, oAuth, Sessions, Cookies etc.
It seems there are alot of options and methods, some of which seem redundant.
The approach Ive cobbled together is as follows:
(all code is abbreviated for clarity - dependencies, supporting code, & error handling have been omitted)
AUTHENTICATION:
Client Side
1) User enters username/password credentials into login form and submits the data.
2) A request containing the login data is sent to my API on submit:
$http.post('https://my.api/login', loginData).then(function(res){ // send request
if(res.data.response.status > 0){ // success
$localStorage.session = { // create session object
uid: res.data.session.user_id,
urole: res.data.session.user_role,
token: res.data.session.token
};
} else console.log('login failed'); // failed
});
Server Side
// check that user exists
$user = db_select('SELECT * FROM users WHERE username=?',array($username));
if(count($user)>0){
if(validate_str($pw, $user['password'])) { // validate hashed password
$res['session'] = sessionCreate($user[0]['id']);
}
}
sessionCreate()
is basically creates DB record storing a generated token, expiration time, and user ID.
$expiry = add2Date(now(),'+2 hours'); // session expires in 2 hours
$token = rando(50); // token is a random alpha-numeric 50 char in length
$query = compileINSERT(array('user_id'=>$user_id,'token'=>$token,'expiry'=>$expiry),'user_session');
$session_id = db_process($query['sql'],$query['val']);
if($session_id>0) return(validateSession($token));
validateSession()
is used later to validate a session but used here to return the newly created session.
As shown above (Client Side), a successful response results in the returned session to be saved as an object in $localStorage
.
ACCESS CONTROL:
Client Side
Once a user has logged-in, access control is handled in the resolve
object of UI-Router
using a service.
There are two components to access control: validating that the session exists and is valid, and validating that the $stateParams
slug of page being attempted to access matches the user session.
.state('base.user', {
url:'/user/:id',
templateUrl: 'views/user.html',
controller: 'userCtrl',
resolve: { // authenticate user and check access control
authenticate: function ($q, $state, $timeout, $localStorage, $stateParams, AuthService){
AuthService.validate($localStorage.session.token).then(function(res){
if(res.data.response.status!==1
|| !AuthService.access($localStorage.session.uid, $stateParams.id)) {
$timeout(function() { $state.go('base.notfound') });
return $q.reject();
} else return $q.resolve();
});
}
}
})
AuthService.validate()
and AuthService.access()
are both defined in the service:
.service('AuthService', function($http){
this.validate = function(token){
// send token to API
$http.get('http://my.api/validate/'+token);
}
this.access = function(uid, stateparam){
// make sure session user_id matches URI slug being accessed
if(Number(uid)===Number(stateparam)) return true;
else return false;
}
})
Server Side
On the back-end, the token is used to validate the session:
$token = $this->req['slug'][0];
if(!empty($token)){
$result = validateSession($token);
if($result['status']>0) $res['session'] = $result['session'];
else if($result['status']<0) $_response['error'][] = 'session expired';
else if($result['status']===0) $_response['error'][] = 'session does not exist';
} else $_response['error'][] = 'token not set';
And finally, validateSession()
is as follows:
function validateSession($token){
$response = array('status'=>0,'session'=>array());
// fetch the session using token
$session = db_select('SELECT * FROM user_session WHERE token=? ORDER BY expiry',array($token))[0];
if(!empty($session)) {
$time_to_expiry = datetimeDiff($session['expiry'],now());
if($time_to_expiry['invert']==0) $response['status'] = -1; // session exists but is expired
else $response['status'] = 1; // session valid
$response['session'] = $session;
}
return($response); // return session or an error
}
SUMMARY
To summarize, authentication and validation as I have written is as follows:
Authentication:
1) user logs in
2) login data is sent to API
3) username/password pair is validated
4) a database record containing the user id, expiry, and unique generated token is created
5) session data is returned in the response JSON and stored in local storage
Access Control:
1) User attempts to access a restricted page
2) token from local storage session data is sent to API
3) API checks whether session with token exists in DB and has not expired
4) API returns an valid/invalid status
additionally,
5) user id from local storage session data is checked against the URI slug for the restricted page (to prevent any authenticated user from accessing any other authenticated user's content)
MY QUESTIONS (finally)
I realize all of the above is a bit naive and just seems too simplistic so here are the questions:
1) I am sending my token in the URL rather than in the request header, what are the drawback (if any) of doing this. Why should the token be sent in the request header?
2) I am not using cookies, however all the pieces seem to be there for a functional authentication/access control protocol. Where would cookie fit in above example?
3) What are some exploits that can be used to bypass the system above?
4) What are some best-practices that I am not holding to in the above system?
5) Any input & critique would be appreciated.