Laravel API Policy returns 403 - laravel

I'm trying to create policies for my Laravel API, so that only the the user can edit their own profile. Maybe these policies do not work the same way for API's.
Controller:
public function updateProfile(Request $request, User $user)
{
$this->authorize('updateProfile', $user->profile);
$validator = Validator::make($request->all(), [
'display_name' => 'required|unique:profiles',
]);
if ($validator->fails())
return response()->json(['error' => $validator->messages()], 422);
$user->profile->update($request->all());
return response(['message' => 'Profile sucessfully updated']);
}
Policy
public function updateProfile(User $user, Profile $profile)
{
return $user->id === $profile->user_id;
}
AuthServiceProvider
protected $policies = [
// 'App\Models\Model' => 'App\Policies\ModelPolicy',
Profile::class => ProfilePolicy::class
];
They return 403 errors. I'm thinking maybe I need to use requests, instead of the user models in the policy? like
$request->user->id === $request->profile->user_id
Any idea what the issue is?

Related

Unauthenticated error when using Sanctum Token to get API Request with domain URL

I am working on a project (web and api) and using Laravel sanctum in my project, I am trying to make an api get request on postman to see user information after logged in but I keep getting this "message": "Unauthenticated." error but I notice that when I was using the get to access user information from my api on localhost (http://127.0.0.1:8000/) it was showing the details and working fine, but when I test it with my domain url (http://sotun.com/mark/live) show Unauthenticated.
My Controller
class AuthApiController extends Controller
{
public function login(Request $request)
{
$request->validate([
'email' => 'required|email',
'password' => 'required',
'device_name' => 'required',
]);
$user = User::where('email', $request->email)->first();
if (! $user || ! Hash::check($request->password, $user->password)) {
throw ValidationException::withMessages([
'email' => ['The provided credentials are incorrect.'],
]);
}
$token = $user->createToken($request->device_name)->plainTextToken;
$response = [
'user' => $user,
'token' => $token,
];
return response ($response, 201);
}
public function user(Request $request)
{
return $request->user();
}
}
api.php
use App\Http\Controllers\AuthApiController;
Route::group(['middleware' => ['auth:sanctum']], function() {
Route::get('/user', [AuthApiController::class, 'user']);
Route::post('/logout', [AuthApiController::class, 'logout']);
});
User Model
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable implements MustVerifyEmail
{
use HasApiTokens, HasFactory, Notifiable;
protected $fillable = [
'name',
'email',
'password',
'user_id',
'user_type',
'user_job',
];
protected $hidden = [
'password',
'remember_token',
];
protected $casts = [
'email_verified_at' => 'datetime',
];
}
In the .env I added the below
.env
SANCTUM_STATEFUL_DOMAIN=http://sotun.com/mark/live
SESSION_DOMAIN=http://sotun.com/mark/live
postman output
But the same result, please I will help to solve this Thanks
Modify your .env file configurations to:
SANCTUM_STATEFUL_DOMAINS="sotun.com,127.0.0.1:8000"
SESSION_DOMAIN=sotun.com
Notice the pluralization of the environmental variable SANCTUM_STATEFUL_DOMAINS.

Return to a View from Controller after an Api call - Laravel 8

So guys,
I have an app that needs to login.
After login and getting the API and token, it has to redirect to a dashboard, but unfortunately, I can't make it to a dashboard view.
I try to find answers on the forum but can't find one that suits my code.
Here is my api.php
Route::post('/login', App\Http\Controllers\api\LoginController::class)->name('login');
my web.php
Route::get('/dashboard', [Controller::class, 'dashboard']);
my LoginController
class LoginController extends Controller
{
/**
* Handle the incoming request.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function __invoke(Request $request)
{
//set validation
$validator = Validator::make($request->all(), [
'email' => 'required',
'password' => 'required'
]);
//if validation fails
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
//get credentials from request
$credentials = $request->only('email', 'password');
//if auth failed
if(!$token = auth()->guard('api')->attempt($credentials)) {
return response()->json([
'success' => false,
'message' => 'Email atau Password Anda salah'
], 401);
}
//if auth success
return response()->json([
'success' => true,
'user' => auth()->guard('api')->user(),
'token' => $token
], 200);
}
my AuthController :
class AuthController extends Controller
{
public function login(Request $request){
$email = $request->input("email");
$password = $request->input("password");
$request = Request::create('http://localhost:8000/api/login', 'POST',[
'name'=>$email,
'password'=>$password,
]);
$response = json_decode(Route::dispatch($request)->getContent());
// echo($response->success);
if($response->success == 1 || true){
return redirect()->route('dashboard',["response"=>$response]);
}else{
return redirect()->back();
}
}
}
Controller.php where dashboard route is defined:
public function dashboard()
{
return view('dashboard', [
"title" => "Dashboard",
]);
}
if I'm using this code, the error I get is:
Route [dashboard] not defined.
but if I'm not using return redirect and use return view instead. I can go to my dashboard, but the URL is localhost:8000\auth\login which is not what I want.
is there any suggestion so I can get my view on Dashboard?
Thank you very much.

Laravel 9: Auth::user() / auth()->user() null after successfull login

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.

How can I delete the token when the user log out?

I made a UserController which generats an accessToken when a user registered succesfully on a page.
class UserController extends Controller
{
/**
* Login Method: in here we call Auth::attempt with the credentials the user supplied.
* If authentication is successful, we create access tokens and return them to the user.
* This access token is what the user would always send along with all API calls to have access to the APIs.
* Register Method: like the login method, we validated the user information,
* created an account for the user and generated an access token for the user.
*/
public function login()
{
$credentials = [
'email' => request('email'),
'password' => request('password')
];
if (Auth::attempt($credentials)) {
$success['token'] = Auth::user()->createToken('MyApp')->accessToken;
return response()->json(['success' => $success]);
}
$status = 401;
$response = ['error' => 'Unauthorized'];
return response()->json($response, $status);
}
public function register(Request $request)
{
$validator = Validator::make($request->all(), [
'name' => 'required',
'email' => 'required|email',
'password' => 'required',
]);
if ($validator->fails()) {
return response()->json(['error' => $validator->errors()], 401);
}
$input = $request->all();
$input['password'] = bcrypt($input['password']);
$user = User::create($input);
$success['token'] = $user->createToken('MyApp')->accessToken;
$success['name'] = $user->name;
return response()->json(['success' => $success]);
}
public function getDetails()
{
return response()->json(['success' => Auth::user()]);
}
}
My problem is that I want to remove the token when the user logs out but I dont know how to remove the access token from the user.
logout function in my UserController
public function logout()
{
Auth::user()->tokens->each(function($token, $key) {
$token->delete();
});
return response()->json([
'message' => 'Logged out successfully!',
'status_code' => 200
], 200);
}
When I test it with postman with the GET route: http://127.0.0.1:8000/api/logout. Am I missing something?
UPDATE
Here s my api.php file:
Route::resource('categories', 'App\Http\Controllers\CategoryController');
Route::post('register', 'App\Http\Controllers\UserController#register');
Route::post('login', 'App\Http\Controllers\UserController#login');
/**
* We can group the routes we need auth for
* under common middleware. It secures our routes
*/
Route::group(['middleware' => 'auth:api'], function(){
Route::get('logout', 'App\Http\Controllers\UserController#logout');
});
I am testing it in postman using the route: http://127.0.0.1:8000/api/logout and passing the Bearer token, which I get from the login request, as a value.
It should be POST Request instead of GET request, because your deleting/making change to the database.
The route should look like this:
Route::POST('logout', 'App\Http\Controllers\UserController#logout')->middleware('auth:api');
And the logout method in in UserController should be.
public function logout()
{
auth()->user()->tokens->each(function ($token, $key) {
$token->delete();
});
return response()->json([
'message' => 'Logged out successfully!',
'status_code' => 200
], 200);
}
In your logout function, it should expire the token, not delete it
public function logout(Request $request)
{
$request->user()->token()->revoke();
return response()->json([], Response::HTTP_NO_CONTENT);
}
OR if you wanna expire all his tokens:
use Illuminate\Support\Facades\Auth;
public function logout(Request $request)
{
$userTokens = Auth::user()->tokens();
foreach($userTokens as $token)
{
$token->revoke();
}
}

Laravel 302 redirect issue

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]);
}
}

Resources