I made a manual login system in a Laravel 9 API that it's works correctly, but when I try to use Auth::user() in another controller, I get it as null, but when I return the auth->user() to the Vue SPA, I get it correctly. Is there a way to it is setting Auth::user() null after a successfull login? Here's are my api.php (api routes):
route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
route::controller(UserController::class)->group(function () {
route::post('/register', 'register');
route::post('/login', 'login');
route::get('/logout', 'logout');
});
route::resource('book', BookController::class);
route::get('/my_books/{user_id}', [BookController::class, 'myBooks']);
As you can see in the image above, I can get the authenticated user after try login it, here's my login method:
public function login(Request $request)
{
$validate = $request->validate([
'email' => 'required|email',
'password' => 'required'
]);
if ($validate) {
$credentials = $request->only('email', 'password');
return Auth::attempt($credentials)
? Auth::user() :
response()->json('No se ha podido iniciar sesiĆ³n', 500);
}
return response()->json($validate->errors, 422);
}
But when I'm going to store a new book, I get the following error:
Here's the error, when I try to use the auth()->user() method to get the logged in user's id:
public function store(Request $request)
{
$validate = $request->validate([
'title' => 'required',
'genre' => 'required'
]);
if ($validate) {
$book = Book::create([
'title' => $request->title,
'author' => $request->author,
'genre' => $request->genre,
'subgenre' => $request->subgenre,
'opinion' => $request->opinion,
]);
$user = User::find(auth()->user()->id);
if ($request->cover) {
$this->uploadImage($request, 'cover', $book);
}
$user->books()->save($book);
return new BooksResource($book);
}
I don't know why it's happening, and I'd like any idea or possible solution. Thanks in advance:
From laravel 9 documentation
// Get the currently authenticated user's ID...
$id = Auth::id();
Also, you should describe your
route::get('/my_books/{user_id}', [BookController::class, 'myBooks']);
route before resource route.
I guess, you dont need this assign $user = User::find(auth()->user()->id); just use auth()->user
To get the Authenticated user, put the book route inside the auth:sanctum middleware.
Related
I was recently updating from laravel's sanctum to passport; and there is this one test that bothers me a lot.
In sanctum there is this method under the PersonalAccessToken model that finds the token and returns the token if it exists.
I don't seem to find anything like that in the docs or online.
I'm validating the test by asserting that $user->tokens is not empty... yet I wish to validate that the token I'm returning from my login controller is indeed a token; not just the creation;
Thnx in advance...
Login Test
public function user_can_login()
{
//$this->withoutExceptionHandling();
$user = User::factory()->create();
$url = route('api.v1.auth.login', [
'email' => $user->email,
'password' => 'password'
]);
$res = $this->jsonApi()
->post($url)
->assertStatus(200);
$token = $res->json(['access_token']);
$this->assertNotEmpty($user->tokens);
}
Login method in authcontroller
public function login(Request $request)
{
$request->validate([
'email' => 'required|email',
'password' => 'required',
]);
$credentials = $request->only(['email', 'password']);
if (Auth::attempt($credentials)) {
$user = Auth::user();
$access_token = $user->createToken('laravel-api.local')->accessToken;
return response()->json(['access_token' => $access_token], 200);
} else {
return response()->json(['error' => 'Unauthorized'], 401);
}
}
pues:dont know why im writing the code, but just for ref of what i'm doing
https://laracasts.com/discuss/channels/testing/how-do-i-create-a-route-while-testing
solution is quite simple... you'll find it here... I had an issue when I tried that before hand and it seems to be with the use of the Route::name('name') method and the route('name') function threw a server error. but if you call the path directly it should work...
any who... authController and login method stay the same but the test changes to...
public function setUp(): void
{
parent::setUp();
Route::middleware('auth:api')
->get('/test-route', function (Request $request) {
return $request->user();
});
$clientRepository = new ClientRepository();
$client = $clientRepository->createPersonalAccessClient(
null,
'Personal Access Client Test',
'/'
);
DB::table('oauth_personal_access_clients')->insert([
'client_id' => $client->id,
'created_at' => date('Y-m-d'),
'updated_at' => date('Y-m-d'),
]);
}
/** #test */
public function user_can_login_with_correct_credentials()
{
//$this->withoutExceptionHandling();
$user = User::factory()->create();
$url = route('api.v1.auth.login', [
'email' => $user->email,
'password' => 'password',
'device_name' => $user->name . ' test Device'
]);
$res = $this->jsonApi()
->post($url)
->assertStatus(200);
$token = $res->json(['access_token']);
$this->jsonApi()
->withHeader('Authorization', 'Bearer ' . $token)
->get('/test-route')
->assertStatus(200);
}
This is my code in LoginController:
public function handleProviderCallback($provider)
{
$socialUser = Socialite::driver($provider)->stateless()->user();
$user = User::where('email', $socialUser->getEmail())->first();
if (!$user) {
$user = User::create([
'name' => $socialUser->getName(),
'email' => $socialUser->getEmail(),
'password' => Hash::make('12345678'),
'social_id' => $socialUser->getId(),
]);
}
Auth::login($user, true);
return redirect()->intended($this->redirectPath());
}
When performing normal login, it redirects to intended. But, in case of social login, it doesn't. What could be the cause?
I had a similar problem. You have to store the path you want to redirect in a session and then redirect the the path intended. I am also using the Socialite. Here is how I solved it.
public function redirect($provider)
{
session()->put('intended_url', url()->previous());
return Socialite::driver($provider)->redirect();
}
public function callback(Request $request, $provider){
$intendedUrl = session('intended_url');
if (!$request->has('code') || $request->has('denied')) {
return redirect('/');
}
$userSocial = Socialite::driver($provider)->stateless()->user();
$users = User::where(['email' => $userSocial->getEmail()])->first();
if($users){
Auth::login($users);
return redirect()->intended($intendedUrl);
}else{
$user = User::create([
'name' => $userSocial->getName(),
'email' => $userSocial->getEmail(),
'provider_id' => $userSocial->getId(),
'provider' => $provider,
]);
Auth::login($user);
return redirect()->intended($intendedUrl);
}
}
Before the redirect I store the previous route and then I use it. So no matter from which URL the user signs in, it will be redirected to the same page.
Check your url()->previous() in my case I actually needed the current url. So I used session()->put('intended_url', url()->full()); in the controller I hit and after lgoin redirect to this url as in the example.
Also you need to use Session::forget('intended_url'); afeter assigning it to variable.
I am trying to get info from action, but when click, just page refresh and in console I get code 302 and stay on current page.
I read a lot of similar topics here but found nothing.
I am trying to execute http://laravel2.lo/getUserChannels?user_id=2
Laravel 5.7.16
route:
Auth::routes();
Route::group(['middleware' => ['auth']], function () {
Route::view('createUser', 'createuser');
Route::view('createChannel', 'createchannel');
Route::view('joinChannel', 'joinchannel');
Route::get('profile', 'UserController#profile');
Route::get('users', 'UserController#users');
Route::get('getChannelUsers', 'UserController#getChannelUsers');
Route::get('getUserChannels', 'ChannelController#getUserChannels');
});
ChannelController:
class ChannelController extends Controller
{
public function getUserChannels(Request $request)
{
$this->validate($request, [
'user_id' => 'required|integer',
]);
/** #var User $user */
$user = User::find($request->user_id);
return view('singleuser', ['channels' => $user->channels, 'username' => $user->name]);
}
}
In the log file no errors.
Thanks for any help and advise.
I don't think you'll receive query params as anything other than strings, so your integer validation fails.
To improve your error handling you could customize your App\Exceptions\Handler, catch your ValidationException errors with something like get_class() or instanceOf and do some neat stuff there
And of course you could not use query params at all by using Route::get('getUserChannels/{id}', 'controller#show'); and access it /getUserChannels/2 - then you could probably validate it as an integer
You could go with
Route::get('getUserChannels/{id}', ...
public function getUserChannels($id)
{
$user = User::findOrFail($id);
return view('singleuser', [
'channels' => $user->channels,
'username' => $user->name
]);
}
Then it would just throw a 404 if string, not found etc...
class ChannelController extends Controller
{
public function getUserChannels(Request $request)
{
$validator = \Validator::make($request->all(), ['user_id' => 'required|integer']);
if($validator->fails())
{
$error = $validator->errors()->first();
dd($error);
}
/** #var User $user */
$user = User::find($request->user_id);
return view('singleuser', ['channels' => $user->channels, 'username' => $user->name]);
}
}
I'm trying to figure out how to test my Passport-driven logout function, which looks like this:
public function logout() {
$accessToken = auth()->user()->token();
$refreshToken = DB::table('oauth_refresh_tokens')
->where('access_token_id', $accessToken->id)
->update([
'revoked' => true
]);
$accessToken->revoke();
return response()->json(['status' => 200]);
}
I am using the Passport actingAs helper in setting up the response.
Passport::actingAs(
$user,
['read write']
);
$response = $this->post('/logout')
->assertStatus(200);
The test fails, as the code gives a 500 error, as auth()->user()->token() is ... empty-ish? $accessToken->id is 0, for example, which it shouldn't be, which means the code fails.
What I'm not sure about is if this is expected behavior because of how Passport's actingAs helper works and I can't actually test the logout function, or if there's something wrong with my logout function. Halp!
My routes:
Route::post('login', 'Auth\LoginController#login');
Route::group(['middleware' => 'auth:api'], function() {
Route::post('logout', 'Auth\LoginController#logout');
});
Route::middleware('auth:api')->get('/user', function (Request $request) {
return $request->user();
});
JsonApi::register('v1', ['namespace' => 'Api'], function (Api $api, $router) {
$api->resource('training-locations');
$api->resource('courses');
});
ETA: My login function, if it's helpful:
public function login(Request $request, Client $client){
$this->validateLogin($request);
if ($this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
$response = $client->post(config('app.url') . '/oauth/token', [
'form_params' => [
'client_id' => config('auth.proxy.client_id'),
'client_secret' => config('auth.proxy.client_secret'),
'grant_type' => config('auth.proxy.grant_type'),
'username' => $request->email,
'password' => $request->password,
'scopes' => '[read write]'
]
]);
if ($response->getStatusCode() === 200) {
$this->clearLoginAttempts($request);
return response($response->getBody()->getContents(), $response->getStatusCode());
}
$this->incrementLoginAttempts($request);
return response($response->getBody()->getContents(), $response->getStatusCode());
}
In order to investigate if this is actually an issue, I tried hitting the endpoint via Postman. It gets into the function, but it does not find the auth()->user(). I tried some other endpoints using the same route group, and it was able to find the auth()->user() with them. What might cause it to go missing like that?
It's a little late, but maybe someone will find it helpful. In order to test the logout function you have provided you'll need to pass the token as authorization header.
$response = $this->post('/logout', [], ['Authorization' => 'Bearer ' . $token])
->assertStatus(200);
I'm not sure why, but the access token can't be retrieved with $user->token() when using actingAs, but it can be retrieved with $user->tokens()->first()
.
When a user logs in I want them to be redirected to their profile page instead of homepage. I have a method in another controller that gets a user profile. Not sure what I need to do since the user profile takes a username variable but when user logs in I'm only asking for email and password.
My route file, but the following method is in a different controller from the authentication controller.
Route::get('/user/{username}', [
'uses' => 'ProfileController#getProfile',
'as' => 'profile.index',
'middleware' => ['auth'],
]);
My following method is in my authentication controller.
public function postSignin(Request $request)
{
$this->validate($request, [
'email' => 'required',
'password' => 'required',
]);
if (!Auth::attempt($request->only(['email', 'password']), $request->has('remember'))) {
return redirect()->back()->with('info' , 'Could not sign you in with that info.');
}
$user= User::where('username', $username)->first();
return redirect()->route('profile.index')
->with('info', 'You are now signed in.')
->with('user', $user);
}
The following is in my profile controller..
public function getProfile($username)
{
$user= User::where('username', $username)->first();
if (!$user){
abort(404);
}
return view('profile.index')
->with('user', $user);
}
To correctly build the route, you need to pass the username here:
$user = User::where('username', $username)->first();
return redirect()->route('profile.index', ['username' => $user->username])
->with('info', 'You are now signed in.')
->with('user', $user);
Get the username from the email provided and pass the $username variable to route:
public function postSignin(Request $request)
{
if (!Auth::attempt($request->only(['email', 'password']),$request->has('remember')))
{
return redirect()->back()->with('info' , 'Could not sign you in with that info.');
}
$username=User::where(['email'=>$request->email])->first()->username;
return redirect()->route('profile.index')->with('username', $username);
}
You can use as like below.
if (!Auth::attempt($request->only(['email', 'password']), $request->has('remember'))) {
return redirect('profile.index')->with('info' , 'Could not sign you in with that info.');