I am building a REST API with Laravel, and I have a filter that checks for a TOKEN:
Route::filter('api.auth', function() {
$token = Request::header('X-CSRF-Token') ? Request::header('X-CSRF-Token') : '';
if (empty($token)) {
return Response::json(
['message' => 'A valid API key is required!'],
401
);
};
$user = User::where('token', '=', $token);
if ($user->count()) {
$user = $user->first();
Auth::login($user);
} else {
return Response::json(
['message' => 'Your token has expired!'],
401
);
};
});
If everything is ok, the filter will log in the user with uth::login($user);
How can I log him for only 1 request?
Since this filter is going to be checked on every request, I think it would be better to log the user out each time.
I have seen this in Laravel's docs, not sure how to apply it:
if (Auth::once($credentials))
{
//
}
Could I have a callback in my response? where I could log the user out?
/*
Get all products.
*/
public function getProducts() {
$products = Auth::user()->products;
return Response::json($products, 200);
}
Any ideas?
If you haven't user's password use this:
if(Auth::onceUsingId($userId)) {
// do something here
}
If I correctly understand the question then I would say that, just replace following
Auth::login($user);
with this (To log the user in only for current request):
Auth::once(['email' => $user->email, 'password' => $user->password]);
If you log in a user only for once then you don't have to manually logo out the user, the user will be asked again for to log in on next request.
Related
I have successfully created a custom user provider and have implemented a user interface.
I am trying to save the user object to my local Postgres database that is returned from a third party backend.
Here is what my retrieveByCredentials method looks like:
// MyCustomUserProvider.php
public function retrieveByCredentials(array $credentials) {
if (empty($credentials)) {
return null;
}
try {
$response = Http::asForm()
->retry(3, 100)
->timeout(5)
->post('https://my-custom-auth-endpoint', $credentials)
->json();
if ($response) {
$user = User::firstOrNew([
'foo' => $response['foo'],
]);
$user->save();
return $user;
}
} catch (RequestException $e) {
Log::info('-----------------------------------------------------------');
Log::info(print_r($e->getMessage(), true));
Log::info('-----------------------------------------------------------');
return $e->getMessage();
}
}
...
public function validateCredentials(Authenticatable $user, array $credentials): bool {
return strtoupper($user->foo) === strtoupper($credentials['foo']);
}
So far, everything is working great. I am getting the correct values/booleans for each required method. However, I need to be able to return additional info to my controller than just a boolean value. For example, if a user's password has expired, this information is coming from my 3rd party database. So far, it looks like returning the user results in a true or false response in my LoginController. If I get false back from my CustomProvider I can set a flash message of, "Invalid Credentials". But that may not always be the appropriate error.
Here is how I am calling the CustomUserProvider:
// LoginController.php
$authenticated = Auth::guard('foo')
->attempt([
'userid' => $request->username,
'password' => $request->password,
]);
// $authenticated is the response from CustomUserProvider.
dd($authenticated) // true or false.
It is understood that in my provider I just want to authenticate the user. How can I return the authenticated user back to the controller? Additionally, how can I return the error back to the controller so I can display a proper error message?
The function Auth::guard('foo')->attempt([]) will also return a boolean to tell user if the authentication is successful or not. If you want to get the auth user, just return Auth::user() after the calling that function, like return response()->json(Auth::user())
I want to make the password optional while login into the system. If the user enters the password the login works fine and return the jwt token, when I entered to try to login only with email it gives the following error:-
Undefined index: password (500 Internal Server Error)
The following is the code of my login method
public function authenticateUser($request)
{
$input = $request->only('email','password');
if (!$authorized = Auth::attempt($input, true)) {
return $this->failure('Credentials doesnot match our records!', 401);
} else {
$token = $this->respondWithToken($authorized);
return $this->success('Login Successfully !', $token, 200);
}
}
protected function respondWithToken($token)
{
return [
'token' => $token,
'token_type' => 'Bearer',
'expires_in' => Auth::factory()->getTTL() * 60,
'user' => Auth::user()
];
}
so basically, what I want is when a user enters an email it will login and should return the token, and if the user login with email and password then it should also work and return the token.
You can create a custom Authentication User Provider that will work around this potentially missing 'password' field. Though, I would probably not here. You can check the input yourself to see if there is a password or not. If there is pass it through attempt like normal. If it is not there find the user using the configured User Provider and login to the guard (what attempt is doing).
Perhaps something like this:
public function authenticateUser($request)
{
if ($request->has('password')) {
$token = Auth::attempt($request->only(['email', 'password']));
} else {
$token = ($user = Auth::getProvider()->retrieveByCredentials($request->only(['email'])))
? Auth::login($user)
: false;
}
return $token
? $this->success('Login Successfully !', $this->respondWithToken($token), 200)
: $this->failure('Credentials do not match our records!', 401);
}
The error that you're getting means that there is no password key in the input array that you're sending via request. This happens on this line:
$input = $request->only('email','password');
In order to bypass that, you would need go get all inputs, or check if those inputs exist and then read from them:
//Get all inputs
$input = $request->input();
//Or get email first, and then check for password
$input['email'] = $request->email;
$input['password'] = $request->filled('password') ? $request->password : null;
Note: Since I can't see your actual login functions, this might not work with only email, since password might be required parameter. If that's the case, you will have to alter those functions.
I have a Google+ login on my app with Laravel Socialite. When the login is done I have a callback to connect the user (I create her in database if necessary).
But I want to restrain the connection to only the company (email like "example#company.com", so only the email with "company.com").
Can I do it with Laravel Socialite ? I can make the verification manually in my callback but if Socialite can do it, it's better.
Thank you
My callback :
public function handleProviderCallback($provider){
$user = Socialite::driver($provider)->user();
if ($user) {
$local_user = User::whereEmail($user->getEmail())->first();
// If we don't have a user create a new user
if (!$local_user) {
$fragment = explode(' ', $user->getName());
$local_user = User::create([
'first_name' => isset($fragment[0]) ? $fragment[0] : '',
'last_name' => isset($fragment[1]) ? $fragment[1] : '',
'email' => $user->getEmail(),
'last_seen' => Carbon::now(),
'password' => ''
]);
$local_user->roles()->attach(Role::whereName('User')->first());
}
auth()->login($local_user);
}
return redirect($this->redirectTo);
}
You have a step by step guide for domain restriction.
In controller you need to specifiy these actions:
public function handleProviderCallback()
{
try {
$user = Socialite::driver('google')->user();
} catch (\Exception $e) {
return redirect('/login');
}
// only allow people with #company.com to login
if(explode("#", $user->email)[1] !== 'company.com'){
return redirect()->to('/');
}
// check if they're an existing user
$existingUser = User::where('email', $user->email)->first();
if($existingUser){
// log them in
auth()->login($existingUser, true);
} else {
// create a new user
$newUser = new User;
$newUser->name = $user->name;
$newUser->email = $user->email;
$newUser->google_id = $user->id;
$newUser->avatar = $user->avatar;
$newUser->avatar_original = $user->avatar_original;
$newUser->save();
auth()->login($newUser, true);
}
return redirect()->to('/home');
}
No, you can’t do it in Socialite itself because Socialite is just a mechanism of retrieving tokens from OAuth-compliant servers.
If you only want to accept users with a particular email suffix, then that’s business logic so something you should handle in your callback:
public function handleProviderCallback()
{
$user = Socialite::driver('google')->user();
if (Str::endsWith($user->getEmail(), '#example.com')) {
// Look up user and authenticate them
}
abort(400, 'User does not belong to organization');
}
I am trying to implement a single signon on multiple domains. The concept is pretty simple i.e to send unique user tokens and then verify these tokens to find the user and then log him in.
Now after verifying the token and then grabbing the user, i do something like this
$loggedInUser = Auth::loginUsingId($user->id, true);
Now i have a custom middleware where it first checks for a logged in user, i.e
Auth::Check()
The above works fine for the first time. But on refresh Auth::check() is not validated. I have also tried using all different session drivers but still doesn't work.
I used a similar code on laravel 5.2, and it did work. But on laravel 5.3 its not validating on persistent requests.
Edit: Let me show you my Code
I have not modified AuthServiceProvider or any other guard. I do have the user model inside a directory but i have modified the path in auth.php.
Here is the route that domain1 points to:
http://domain2.com/{{$role}}/{{$route}}/singlesignon/{{$token}}
This is then picked up by verifySingleSignOn method inside the loginController which takes in the role, route that the user came in from other domain and the token. The user is then redirected to the same routes, but on domain2. Here i can successfully recieve the user id before manually logging in.
public function verifySingleSignOn($role, $route, $token)
{
// Fetch Single Signon
$userRepository = new UserRepository();
$user = $userRepository->checkForSingleSignOnToken($token, ['id']);
// Check if Token Exists
if (isset($user->id) && is_int($user->id) && $user->id != 0) {
// Manually Logging a user (Here is successfully recieve the user id)
$loggedInUser = Auth::loginUsingId($user->id);
if (!$loggedInUser) {
// If User not logged in, then Throw exception
throw new Exception('Single SignOn: User Cannot be Signed In');
}
$redirectTo = $role . '/' . $route;
return redirect($redirectTo);
} else {
return Auth::logout();
}
}
Then i have this GlobalAdminAuth middleware
// Check if logged in
if( Auth::Check() ){
$user = Auth::User();
// Check if user is active and is a globaladmin
if( !$user->isGlobalAdmin() || !$user->isActive() ){
return redirect()->guest('login');
}
}else{
return redirect()->guest('login');
}
return $next($request);
Now the first time everything works fine and the user moves through the middleware successfully . but the second time the else statement is triggered.
Edit: Code for checkForSingleSignOnToken
public function checkForSingleSignOnToken($token, $columns = array('*'))
{
return User::where('single_signon', $token)->first($columns);
}
try
Auth::login($user);
instead of
Auth::loginUsingId($user->id, true);
Cookies are restricted domain-wise. Your application on domain1.com wont be able to grab cookies set by domain2.com.
You should be customizing the guard to use some other mechanism than cookies. Maybe use a token in the query parameters.
add this to your protected $middleware array in app\Http\Kernel.php
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class
I think it has to do with an update in the framework
no using auth:check in middleware
using request->user() or auth::user()
Please review bellow code structure, i had made manual authentication
in laravel 5.0.
routes.php
Route::get('login_user_by_id/{id?}', ['as' => 'login_user_by_id', 'uses' => 'UsersController#login_user_by_id']);
Route::post('user_login_post_for_admin',['as'=>'user_login_post_for_admin','uses'=>'LoginController#user_login_post_for_admin']);
Route::get('user_logout', ['as' => 'user_logout', 'uses' => 'UsersController#user_logout']);
LoginController.php
public function user_login_post_for_admin(){
$this->set_email($_POST['email']);
$this->set_password($_POST['password']);
$this->set_login_requested_role(['Admin','Moderator']);
return $this->user_login_post();
}
public function user_login_post(){
$User = new User();
if(isset($this->email) && !empty($this->email)){
$User->set_email(trim($this->email));
$User->set_password(Hash::make(trim($this->password)));
$user_login_data = $User->check_email_password_for_login();
if(isset($user_login_data) && !empty($user_login_data)){
if (Hash::check(trim($this->password), $user_login_data[0]->password)) {
$response['user_id']=$user_login_data[0]->id;
$response['name']=$user_login_data[0]->name;
$response['surname']=$user_login_data[0]->surname;
$response['profile_picture']=$user_login_data[0]->profile_picture;
$response['SUCCESS']='True';
$response['MESSAGE']='Login Success.';
return Redirect::route('login_user_by_id',[$user_login_data[0]->id]);
}else{
Session::put('SUCCESS','FALSE');
Session::put('MESSAGE', 'Invalid Credential.');
return redirect()->back();
}
}else{
Session::put('SUCCESS','FALSE');
Session::put('MESSAGE', 'Invalid Credential.');
return redirect()->back();
}
}else{
Session::put('SUCCESS','FALSE');
Session::put('MESSAGE', 'Invalid Credential.');
return redirect()->back();
}
}
UsersController.php
public function login_user_by_id($id=''){
if(isset($_GET['id'])&&!empty($_GET['id'])){
$id = $_GET['id'];
}
$User = new User();
$Log=new Log();
$user_for_auth = $User->find($id);
Auth::login($user_for_auth, true);
$User->id=AUTH::user()->id;
$auth_user_role=$User->auth_user_role();
$rl_title=$auth_user_role[0]->rl_title;
return Redirect::route('admin_home');
}
public function user_logout(User $user){
$User=new User();
$login_user_id = AUTH::user()->id;
$User->id=AUTH::user()->id;
$auth_user_role=$User->auth_user_role();
$login_user_role=$auth_user_role[0]->rl_title;
$response['user_id']=$login_user_id;
$response['SUCCESS']='TRUE';
$response['MESSAGE']='Successfully Logout.';
Auth::logout();
return Redirect::route('admin_login');
}
Upon login, I return the user object + session token in JSON form, so that the mobile device that connects to my application can be authenticated.
However, I have a difficulty understanding how would I go about authenticating the user only with his session id?
Once logged in, the mobile device sends the session token upon every request, which means I somehow need to check whether it's the same user (using a custom auth filter).
How would I do it?
You may have a table for saving tokens
Add a filter in routes.php
Route::group(array('before' => 'auth'), function() { ... })
And in the filters.php you can search the token in the database, if isn't exist you return a no access response
Route::filter('auth', function () {
$input_token = Input::get('token');
if (!empty($input_token)) {
$validator = Validator::make(
['token' => $input_token],
['token' => 'token']
);
if (!$validator->fails()) {
$token = Token::where('hash', $input_token)->first();
if ($token) {
$user = User::find($token->user_id);
if ($user) {
Auth::login($user);
return;
}
}
}
}
$response = Response::make(json_encode([
'error' => true,
'messages' => [
Lang::get('errors.NO_ACCESS')
]
]), 200);
$response->header('Content-Type', 'application/json');
return $response;
});
You could do it like this:
$sessionID = '4842e441673747d0ce8b809fc5d1d06883fde3af'; // get this from \Session::getId(); from your previous authenticated request (after logging in because it changes).
$s = new \Illuminate\Session\Store(NULL, \Session::getHandler(), $sessionID);
$s->start();
$userID = $s->get('login_82e5d2c56bdd0811318f0cf078b78bfc');
\Session::set('login_82e5d2c56bdd0811318f0cf078b78bfc', $userID);
return \Auth::user();
Not the prettiest code but it works. It creates an instance of a session using the previous Session ID, then start loads it up from file. The user ID is in that key, so then it just sets the user id on the current session. Then when you call Auth::user() it loads up the User using that user id.
The reason for all the numbers in the key is because the larval developer thought it would be smart to hash the Auth class name to make the key as unique as possible... :-S