What I'm looking to do is
Authenticate users on bar.com and
Post their credentials to foo.com/login and re-authenticate them without needing to log in again.
Currently, to GET secure pages on foo.com I'm using form-based access via the SecurityServiceProvider and a db-backed UserProvider to authenticate. Works great: any attempt to load a secured route is intercepted by the firewall and then redirected after successful authentication.
What I can't figure out is how to pass the POST variables (username and password) on to the provider instance and forward the user to the supplied route.
Stub POST route:
$app->post('/login', function(Request $req) use ($app) {
$route = $req->request->filter('route');
$username = $req->get('username');
$password = $req->get('password');
/* magic happens...? */
});
Here is an example of using the user provider to load a user check the password matches then setting the token in the security service. So if you put this code into a route you can get access to the Request for your username and password.
$userProvider = $app['security.user_provider.default'];
$user = null;
try {
$user = $userProvider->loadUserByUsername($username);
} catch (UsernameNotFoundException $e)
{
;
}
$encoder = $app['security.encoder_factory']->getEncoder($user);
// compute the encoded password
$encodedPassword = $encoder->encodePassword($password, $user->getSalt());
// compare passwords
if ($user->password == $encodedPassword)
{
// set security token into security
$token = new UsernamePasswordToken($user, $password, 'yourProviderKeyHere', array('ROLE_USER'));
$app['security']->setToken($token);
// redirect or give response here
} else {
// error feedback
}
Related
Is there a build-in possibility in Laravel to access the username and pw that was entered previously via the browsers http-auth-basic-dialouge when using http auth basic?
I basically need the username and pw values that should be stored in the corresponding session cookie.
I am doing it like this now:
public function fn()
{
$user = Auth::user();
if(!$user || $user->getAttributeValue('additional_attribute') == 0)
{
return abort(401,'Sorry '.($user ? $user->name : '').', you are not authorized to access this page.');
}
return view('view');
}
I'm currently migrating from a WordPress to a Symfony2 website.
I imported all my WP user on Symfony2 but now I'm looking for a way to make additional checks when the user tries to log in (typically check if the user was imported from WP and check his old password).
What's the best way to add some checks on the User authentication ? (login_check on fosuserbundle).
I simply try to override the SecurityController, but it doesn't work as the login doesn't seem to be made here.
Thanks for your help.
Edit: I need to add my check during the login process, not after. During the login, if the user comes from WordPress, I want to check if the password he provides is the same as his old WordPress password (that is stored in the DB too).
I finaly found a solution, but not sure it's the best way to do the stuff.
I added a listener when the login failed and check if it's a user from WordPress.
Now I'm looking for a solution to handle the "remember me" checkbox because the user is a authenticate programmatically. Here is the code :
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
$username = $request->request->get('_username');
$password = $request->request->get('_password');
$user = $this->doctrine->getRepository('AppBundle:User')->findOneByUsername($username);
if ($user instanceof User && $user->getFromWordpress() == true) {
//The class use by WordPress to check / encode passwords
$hasher = new PasswordHash(8, TRUE);
//User provide the right password
if ($hasher->CheckPassword($password, $user->getWordpressPassword())){
//Programmatically authenticate the user
$token = new UsernamePasswordToken($user, $user->getPassword(), "main", $user->getRoles());
$this->tokenStorage->setToken($token);
$event = new InteractiveLoginEvent($request, $token);
$this->eventDispacher->dispatch("security.interactive_login", $event);
//Set the password with the Symfony2 encoder
$encoder = $this->encoderFactory->getEncoder($user);
$password = $encoder->encodePassword($password, $user->getSalt());
$user->setPassword($password);
$user->setFromWordpress(false);
$this->doctrine->getManager()->persist($user);
$this->doctrine->getManager()->flush();
//Finnaly send login ok response
return $this->onAuthenticationSuccess($request, $token);
}
}
//Login failed code ...
//.....
}
I'm working on an RESTful API on where users are allowed to provide their user information in a HTTP header named x-wsse.
Example: UsernameToken Username="JohnDoe", PasswordDigest="PasswordDigest", Nonce="Nonce", Created="CreatedTimestamp"
PasswordDigest = base64_encode(sha1(base64_decode($nonce).$created.$secret, true));
$secret is the hashed, salted password.
This header gets "caught" by a listener which "parses" it and validates it.
The code snippet:
$request = $event->getRequest();
$wsseRegex = '/UsernameToken Username="([^"]+)", PasswordDigest="([^"]+)", Nonce="([^"]+)", Created="([^"]+)"/';
if (!$request->headers->has('x-wsse') || !preg_match($wsseRegex, $request->headers->get('x-wsse'), $matches)) {
return;
}
$token = new WsseUserToken();
$token->setUser($matches[1]);
$token->digest = $matches[2];
$token->nonce = $matches[3];
$token->created = $matches[4];
try {
$authToken = $this->authenticationManager->authenticate($token);
$this->tokenStorage->setToken($authToken);
return;
} catch (AuthenticationException $failed) {
// Authentication failed, some stuff will happen here
}
The authentication manager has a authenticate method:
public function authenticate(TokenInterface $token)
{
$user = $this->userProvider->loadUserByUsername($token->getUsername());
if ($user && $this->validateDigest($token->digest, $token->nonce, $token->created, $user->getPassword())) {
$authenticatedToken = new WsseUserToken($user->getRoles());
$authenticatedToken->setUser($user);
return $authenticatedToken;
}
throw new AuthenticationException('The WSSE authentication failed.');
}
The validateDigest method does this:
protected function validateDigest($digest, $nonce, $created, $secret)
{
// Check created time is not in the future
if (strtotime($created) > time()) {
return false;
}
// Expire timestamp after 5 minutes
if (time() - strtotime($created) > 300) {
return false;
}
// Validate that the nonce is *not* used in the last 5 minutes
// if it has, this could be a replay attack
if (file_exists($this->cacheDir.'/'.$nonce) && file_get_contents($this->cacheDir.'/'.$nonce) + 300 > time()) {
throw new NonceExpiredException('Previously used nonce detected');
}
// If cache directory does not exist we create it
if (!is_dir($this->cacheDir)) {
mkdir($this->cacheDir, 0777, true);
}
file_put_contents($this->cacheDir.'/'.$nonce, time());
// Validate Secret
$expected = base64_encode(sha1(base64_decode($nonce).$created.$secret, true));
if ($digest === $expected) {
$valid = true;
} else {
$valid = false;
}
return $valid;
}
All this is covered in "The Cookbook" of Symfony2. Code and info: http://symfony.com/doc/current/cookbook/security/custom_authentication_provider.html
As advised, all the user passwords are hashed with Bcrypt and - as advised (see http://php.net/password_hash) - I let PHP generate a hash for each password.
I'm testing with Chrome REST console and my WSSE header is generated by: http://www.teria.com/~koseki/tools/wssegen/.
Everything goes well, until I I"m validating the PasswordDigest. I'm getting a mismatch problem.
This is caused by an "invalid salt revision".
Since PHP generates my salt, and I can not find it stored anywhere (user table only has columns id, username, password, email, is_active), I can not "salt" the password myself at the "right" moment.
How should I deal with this problem?
I've been thinking of:
Sending the plaintext username and password through a HTTPS request to a Symfony Controller which then returns the hashed password. This hashed password can be stored then to be reused in the next requests with "fresh" x-wsse header.
Sending the plaintext username and password through a HTTPS request to a Symfony Controller which then returns a valid x-wsse header which can be used for only one request, because the nonce is only allowed to be used once.
I'm not very happy with the solutions above and would like to know what you guys think... PHP is generating a salt itself, but where does it store that? Can I create a JS function which does the same / complies with http://php.net/password_hash?
The explanation of using wsse with Symfony 2 on http://obtao.com/blog/2013/06/configure-wsse-on-symfony-with-fosrestbundle/ says salts may be public as long as they are unique for each user.
We implement wsse authentication to our symfony REST endpoints. But we dont use the password field. We added a new field called token to the user entity. The REST api is consumed by mobile app. When users login to the app using either facebook OR their username and password, the credentials/fb access token are sent to symfony app over https, the server responds with the user profile and a fresh token ( token is regenerated on every login ).
We are using this bundle tough. https://github.com/escapestudios/EscapeWSSEAuthenticationBundle
So when you are validating the digest, you would build the expected digest using the user's token.
So, this is similar to the step 1 you suggested, just that you don,t give away password and salt. You'll have the flexibility to change the user's token without changing the password.
In any case, the client will need to enter their username and password in order to actually authenticate first and retrieve the token.
Even if you implement oAuth, the user will need to first authenticate to your service provider in order to authorize your app.
I created an Android App that communicates with an API I set up using PHP and am going to attempt to rewrite the PHP stuff using Laravel. One of the big changes I want to make is to allow OAuth login into my app that is associated with a user account on my local servers and I am confused about the application flow involved to do this.
Should I do my OAuth authentication on my Android app before registering, then store some sort of credential along with user information when I call my API to create my local user account?
Also, when logging a user in using OAuth, how do I know that the person who is authenticated by the provider, say Facebook, Is associated with a local user account on my servers? Is there some sort of token I can store?
Thanks for any overview information you can provide.
I did the same (without OAuth provider from my site) with HybridAuth There is also a composer package at packagist.org.
I have two tables for that:
users with columns id, email, password (table with my normal users and user infos)
authentications with columns: id, user_id, provider, provider_uid (authentication table with the stuff for OAuth.)
The Code I have in my AuthController (for the route /login/social Route::controller('login','AuthController');)
<?php
class AuthController extends BaseController {
//#see http://www.mrcasual.com/on/coding/laravel4-package-management-with-composer/
public function getSocial($action = '')
{
// check URL segment
if ($action == 'auth') {
// process authentication
try {
Hybrid_Endpoint::process();
}
catch (Exception $e) {
// redirect back to http://URL/social/
return Redirect::to('login/social');
}
return;
}
try {
// create a HybridAuth object
$socialAuth = new Hybrid_Auth(app_path() . '/config/hybridauth.php');
// authenticate with provider
if($action != ''){
// only allow facebook and google
if(in_array($action,array('google','facebook'))){
$provider = $socialAuth->authenticate($action);
}else{
// catch this form_error in the login form
return Redirect::to('login')->withErrors(array('form_errors'=>'The url was invalid'));
}
}else{
return Redirect::to('login');
}
// fetch user profile
$userProfile = $provider->getUserProfile();
}
catch(Exception $e) {
// exception codes can be found on HybBridAuth's web site
return Redirect::to('login')->withErrors(array('form_errors'=>'Error on Login: #'.$e->getCode().': '.$e->getMessage()));
}
/*access user profile data
echo "Connected with: <b>{$provider->id}</b><br />";
echo "As: <b>{$userProfile->displayName}</b><br />";
echo "<pre>" . print_r( $userProfile, true ) . "</pre><br />";
die();*/
//check if User exists
if($user_id = DB::table('authentications')->where('provider', $provider->id)->where('provider_uid',$userProfile->identifier)->pluck('user_id')){
//login user
Auth::loginUsingId($user_id);
//update user details (eg photo, name, etc)
//Here you can update the user details
return Redirect::to('dashboard');
}else{
//lets see if we already know this email -> connect it with the registered account
if($user = User::where('email',$userProfile->email)->first()){
$user->authentications()->save(new Authentication(array('provider'=>$provider->id, 'provider_uid'=>$userProfile->identifier)));
Auth::login($user);
//here you can update the user details
return Redirect::to('dashboard')->with('user_social_linked',$provider->id);
}else{
//register user
$user = $this->createUser(array('email'=>$userProfile->email,'password'=>''));
$user->authentications()->save(new Authentication(array('provider'=>$provider->id, 'provider_uid'=>$userProfile->identifier)));
Auth::login($user);
//here you can set/update the user details
return Redirect::to('dashboard')->with('user_created',true);
}
}
}
private function createUser($credentials)
{
$user = new User();
$user->email = $credentials['email'];
$user->password = $credentials['password'];
$user->save();
Auth::login($user);
return $user;
}
}
The User model has also the following function
<?php
public function setPasswordAttribute($password){
//disable blank passwords => disables account login (eg login only with third party aka social providers)
if($password != ''){
$this->attributes['password'] = Hash::make($password);
}else{
$this->attributes['password'] = $password;
}
}
Now you can treat your own OAuth provider just as any other OAuth provider, eg by adding / configuring a hybridauth provider.
To your questions:
Should I do my OAuth authentication on my Android app before registering, then store some sort of credential along with user information when I call my API to create my local user account?
I would handle it the same as with any other OAuth provider: like in the example above one function to call (I only use login via the Laravel Auth, so I do not have the issues with expiring provider sessions and the user does not have to create another account).
Also, when logging a user in using OAuth, how do I know that the person who is authenticated by the provider, say Facebook, Is associated with a local user account on my servers? Is there some sort of token I can store?
This is done by the user_id column in the authentications table.
Hope that helps.
Could anybody brief about user_token functionality in Auth module? What is a use and how this incorporates in Auth module?
It is used when a user checks the 'Remember me' box on your site. A token is generated for the user and stored in the user_tokens table.
If you look at the Kohana_Auth_ORM class in the _login function, you can see how it is created:
if ($remember === TRUE)
{
// Create a new autologin token
$token = ORM::factory('user_token');
// Set token data
$token->user_id = $user->id;
$token->expires = time() + $this->config['lifetime'];
$token->save();
// Set the autologin cookie
cookie::set('authautologin', $token->token, $this->config['lifetime']);
}
It is used by the auto_login() function also in the Kohana_Auth_ORM class:
/**
* Logs a user in, based on the authautologin cookie.
*
* #return boolean
*/
public function auto_login()
{
if ($token = cookie::get('authautologin'))
{
// Load the token and user
$token = ORM::factory('user_token', array('token' => $token));
if ($token->loaded() AND $token->user->loaded())
{
if ($token->user_agent === sha1(Request::$user_agent))
{
// Save the token to create a new unique token
$token->save();
// Set the new token
cookie::set('authautologin', $token->token, $token->expires - time());
// Complete the login with the found data
$this->complete_login($token->user);
// Automatic login was successful
return TRUE;
}
// Token is invalid
$token->delete();
}
}
return FALSE;
}
It is up to you to correctly use this capability within your authorization controller. I'm relatively new to Kohana, but I perform a simple check to redirect a user if they go to the login form and are already logged in or can automatically login:
if (Auth::instance()->logged_in() || Auth::instance()->auto_login())
Request::instance()->redirect('auth/');
The code for the Auth module isn't too difficult to understand. If you're new to Kohana, it's a good starting point to see how the ORM module works.