How to authorize before run a function in Controller| Laravel - laravel

I have an API task where I should authorize before deleting data (BookReview),
Access to this endpoint requires authentication with an API token and admin privileges.
in User table I have api_token and is_admin field:
[![User Table][1]][1]
this is my BookReviewController:
public function destroy(int $bookId, int $reviewId, Request $request)
{
// #TODO implement
$check_bookReview = BookReview::firstWhere('id', $reviewId);
if ($check_bookReview) {
BookReview::destroy($reviewId);
return response()->noContent();
} else {
abort(404);
}
}
i don't know what to add in my controller, and further more I need to authorize it from postman
[![Postman][2]][2]
the delete function works fine, but there is no authorization yet
[1]: https://i.stack.imgur.com/ywvC8.png
[2]: https://i.stack.imgur.com/fyhpG.png

If you have is_admin in your DB so you can check user is admin or not.
public function destroy(int $bookId, int $reviewId, Request $request)
{
// #TODO implement
$check_bookReview = BookReview::firstWhere('id', $reviewId);
if ($check_bookReview && auth()->user()->is_admin == 1) {
BookReview::destroy($reviewId);
return response()->noContent();
} else {
abort(404);
}
}
or in your controller:
use Auth;
if (Auth::user() && Auth::user()->is_admin == 1) {
//destroy
}
and if you want use middleware then:
in routes/web.php add:
Route::group(['middleware' => ['auth', 'admin']], function () {
Route::post('/destroy-something', 'YourController#destroy')->name('destroy.review');
});
in app/Http/Middleware create a middleware for admin.
then add it to app/Http/Kernel.php to the protected $routeMiddleware array.

You should use some middleware. The middleware is the authetication step that you are missing. In the middleware, you should make the validation like find the user by de token that he sent to you, see if he is an admin an then send the request to the controller (if he is an admin) or return an 401 status code if he is not authorized to do this.
To help you with middleware is nobody better then the documentation

Related

How to register middleware in routes file to be handled after request is processed

I have a login route that makes sure that a user is verified before logging in like so.
Route::post('/login', [AuthController::class, 'login'])->middleware('userIsVerified');
However in the case of the user entering a wrong password, it passes through the middleware first and then returns that the user is not verified in case of the user is not verified.
How to tweak this middleware to make it run after the request is processed by the controller?
Here is the middleware code:
class UserIsVerified
{
public function handle(Request $request, Closure $next)
{
if (auth()->user()) {
$userIsVerified = auth()->user()->verified;
} else {
$user = Client::where('phone', $request->phone)->first();
if (!$user)
return response(['message' => 'User doesnt exist.'], Response::HTTP_NOT_FOUND);
$userIsVerified = $user->verified;
}
if (!! $userIsVerified)
return $next($request);
else
return response(['message' => 'User is not verified.'], Response::HTTP_UNAUTHORIZED);
}
}
Laravel middleware has one more method callled terminate(), this method apply after the response. Its called terminable middleware.
Read More About terminate

Authorize function is not working with middleware | Laravel

I have an authorization using middleware where Function could only run when authorized
this is my middleware:
class IsAdmin
{
public function handle($request, Closure $next)
{
if (auth()->check() && auth()->user()->is_admin == 1) {
return $next($request);
}
return abort(403, 'Forbidden');
}
}
my Controller:
public function destroy(int $bookId, int $reviewId, Request $request)
{
// #TODO implement
$check_bookReview = BookReview::firstWhere('id', $reviewId)->where('book_id', $bookId);
if ($check_bookReview && isAdmin()) {
BookReview::destroy($reviewId);
return response()->noContent();
} else {
abort(404);
}
}
and my api.php as well my Kernel:
'auth.admin' => \App\Http\Middleware\IsAdmin::class
Route::group(['middleware' => ['auth.admin']], function (){
Route::post('/books', 'BooksController#store');
Route::post('/books/{id}/reviews', 'BooksReviewController#store');
Route::delete('/books/{bookId}/reviews/{reviewId}', 'BooksReviewController#destroy');
});
and i have a User db field where it contains api_token and is_admin like below:
and my Postman still return 403 forbidden while i already gave an authorization by headers:
what should i do here, to fulfill my function?
Looks like your Authenticate middleware is not working, so it likely fails on auth()->check().
Make sure to use the auth middleware from Laravel, you can also use a guard as described here:
https://laravel.com/docs/9.x/authentication#protecting-routes

Login and verify user - Call to a member function getKey() on null

I am trying to create a custom verification flow, where as soon as a user clicks the verification link, it logs him in and also verifies him, instead of first making him log in and only then the verification link works.
I built a custom notification URL in my CustomVerificationNotification, including the registered user_id, to login him later:
protected function verificationUrl($notifiable)
{
if (static::$createUrlCallback) {
return call_user_func(static::$createUrlCallback, $notifiable);
}
return URL::temporarySignedRoute(
'verification.custom-verify',
Carbon::now()->addMinutes(Config::get('auth.verification.expire', 60)),
[
'id' => $notifiable->getKey(),
'hash' => sha1($notifiable->getEmailForVerification()),
'user_id' => $this->user->id
]
);
}
Then in my web.php I added this route:
Route::get('/email/verify/{id}/{hash}/{user_id}','Auth\CustomVerifyController#login_and_verify')->name('verification.custom-verify');
Then in my CustomVerifyController:
public function login_and_verify(EmailVerificationRequest $request)
{
//..
}
But I get Call to a member function getKey() on null. And I can't edit EmailVerificationRequest, so what can I do? Is it possible to somehow call Auth::login($user); before calling the EmailVerificationRequest? (Because I have the user_id from the route)
I tried to follow the best answer from this post as well: How to Verify Email Without Asking the User to Login to Laravel
But I'm not sure then how to trigger the verify() method from the web.php and send the $request when I'm first calling the verify_and_login method
First you need verify that the URL is signed by adding the middleware signed
You don't want that anoyone having the url /email/verify/{id}/{hash}/{user_id} able to access this ressource without the signature.
web.php
Route::get('/email/verify/{id}/{hash}/{user_id}','Auth\CustomVerifyController#login_and_verify')
->middleware('signed')
->name('verification.custom-verify');
Then you need to verify that the hash correspond the user_id and for that you can use a Request or a Middleware. I think the Request fits better since Laravel already uses a Request for this.
CustomEmailVerificationRequest.php
<?php
namespace App\Http\Requests;
use Illuminate\Auth\Events\Verified;
use Illuminate\Foundation\Http\FormRequest;
class EmailVerificationRequest extends FormRequest
{
public function authorize()
{
$user = User::findOrFail($this->route('id'));
if (! hash_equals((string) $this->route('hash'), sha1($user->getEmailForVerification()))) {
return false;
}
return true;
}
}
Finally you need to login with the user and set is email as verified
CustomVerifyController.php
public function login_and_verify(CustomEmailVerificationRequest $request)
{
$user = User::findOrFail($this->route('id'));
Auth::login($user);
$user->markEmailAsVerified();
event(new Verified($user));
...
}
[Edit to add addition feature from comments]
In order to have a middleware that verify the signed URL and resend automatically the verification email, you need to build a custom middleware.
ValidateSignatureAndResendEmailVerification.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Routing\Exceptions\InvalidSignatureException;
use URL;
class ValidateSignatureAndResendEmailVerification
{
public function handle($request, Closure $next, $relative = null)
{
if(! URL::hasCorrectSignature($request, $relative !== 'relative')( {
throw new InvalidSignatureException;
}
if (URL::signatureHasNotExpired()) {
return $next($request);
}
return redirect()->route('resend-email-confirmation');
}
}
Then you need to add the middleware to Kernel.php
Kernel.php
protected $routeMiddleware = [
...
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'signed.email' => \App\Http\Middleware\ValidateSignatureAndResendEmailVerification::class,
...
];
Then, don't forget to update your route with the new middleware
web.php
Route::get('/email/verify/{id}/{hash}/{user_id}','Auth\CustomVerifyController#login_and_verify')
->middleware('signed.email')
->name('verification.custom-verify');

Laravel: Create middleware for Sociailite Auth

I'm using JavaScript to receive an access token to the user facebook account.
When I get the token, I send a request to the server with the token to get information about the user using Socialite.
$user = Socialite::driver('facebook')->userFromToken($request->token);
return $user->getName();
Now I'm trying to group API calls that only authenticated user can make, for example /my-profile, my-data, etc...
The middleware purpose is to check if the user is authenticated for a group of API calls instead typing the same condition in all methods:
if ($user->getName()) {
...Execute...
}
Any idea how I can place the following condition for a list of routes for autehnticated users?
My condition is simple:
$user = Socialite::driver('facebook')->userFromToken($request->token);
if ($user->getName()) {
return true;
}
else { return false; }
Okay,
First create the middleware:
php artisan make:middleware name_of_your_middleware
After that, inside the middleware file that has been created:
public function handle($request, Closure $next)
{
$user = Socialite::driver('facebook')->userFromToken($request->token);
if ($user->getName()) {
return $next($request);
}
return redirect('some_other_route_for_error');
}
Then assign the middleware to the routes middelware in app/Http/Kernel.php
protected $routeMiddleware = [
//.......
'name_of_your_middleware' => \Illuminate\Auth\Middleware\Authenticate::class,
];
In your Route file (api.php):
Route::group(['middleware'=>['name_of_your_middleware']],function(){
//your routes
});
Hope it helps :)

How to logout a user from API using laravel Passport

I'm currently using 2 projects. 1 front end (with laravel backend to communicate with API) and another laravel project (the API).
Now I use Laravel Passport to authenticate users and to make sure every API call is an authorized call.
Now when I want to log out my user, I send a post request to my API (with Bearer token) and try to log him out of the API (and clear session, cookies,...)
Then on the client I also refresh my session so the token is no longer known. Now when I go back to the login page, it automatically logs in my user. (Or my user is just still logged in).
Can someone explain me how to properly log out a user with Laravel passport?
Make sure that in User model, you have this imported
use Laravel\Passport\HasApiTokens;
and you're using the trait HasApiTokens in the User model class using
use HasApiTokens
inside the user class.
Now you create the log out route and in the controller,
do this
$user = Auth::user()->token();
$user->revoke();
return 'logged out'; // modify as per your need
This will log the user out from the current device where he requested to log out. If you want to log out from all the devices where he's logged in. Then do this instead
$tokens = $user->tokens->pluck('id');
Token::whereIn('id', $tokens)
->update(['revoked'=> true]);
RefreshToken::whereIn('access_token_id', $tokens)->update(['revoked' => true]);
Make sure to import these two at the top
use Laravel\Passport\RefreshToken;
use Laravel\Passport\Token;
This will revoke all the access and refresh tokens issued to that user. This will log the user out from everywhere. This really comes into help when the user changes his password using reset password or forget password option and you have to log the user out from everywhere.
You need to delete the token from the database table oauth_access_tokens
you can do that by creating a new model like OauthAccessToken
Run the command php artisan make:model OauthAccessToken to create the model.
Then create a relation between the User model and the new created OauthAccessToken Model , in User.php add :
public function AauthAcessToken(){
return $this->hasMany('\App\OauthAccessToken');
}
in UserController.php , create a new function for logout:
public function logoutApi()
{
if (Auth::check()) {
Auth::user()->AauthAcessToken()->delete();
}
}
In api.php router , create new route :
Route::post('logout','UserController#logoutApi');
Now you can logout by calling posting to URL /api/logout
This is sample code i'm used for log out
public function logout(Request $request)
{
$request->user()->token()->revoke();
return response()->json([
'message' => 'Successfully logged out'
]);
}
Create a route for logout:
$router->group(['middleware' => 'auth:api'], function () use ($router) {
Route::get('me/logout', 'UserController#logout');
});
Create a logout function in userController ( or as mentioned in your route)
public function logout() {
$accessToken = Auth::user()->token();
DB::table('oauth_refresh_tokens')
->where('access_token_id', $accessToken->id)
->update([
'revoked' => true
]);
$accessToken->revoke();
return response()->json(null, 204);
}
I am using Laravel 6.12.0, below function is working for me.
public function logout(Request $request){
$accessToken = Auth::user()->token();
$token= $request->user()->tokens->find($accessToken);
$token->revoke();
$response=array();
$response['status']=1;
$response['statuscode']=200;
$response['msg']="Successfully logout";
return response()->json($response)->header('Content-Type', 'application/json');
}
This is my first post.. and i find a clean solution (Laravel last Version)
/**
* Logout api
*
* #return \Illuminate\Http\Response
*/
public function logout(Request $request)
{
if (Auth::check()) {
$token = Auth::user()->token();
$token->revoke();
return $this->sendResponse(null, 'User is logout');
}
else{
return $this->sendError('Unauthorised.', ['error'=>'Unauthorised'] , Response::HTTP_UNAUTHORIZED);
}
}
Below is the simplest way I found to do it.
1. USE database SESSION INSTEAD OF file SESSION
Official documention
php artisan session:table
php artisan migrate
Replace SESSION_DRIVER=file by SESSION_DRIVER=database in your .env file.
2. DELETE USER SESSION RIGHT AFTER LOGIN
After a user is redirected to your frontend and logs in to finally get a token, you probably call a route in api/routes.php to get the user information, that's where I'm closing the user backend session before sending back user information to the frontend:
Route::middleware('auth:api')->get('/user', function (Request $request) {
// Close user session here
Illuminate\Support\Facades\DB::table('sessions')
->whereUserId($request->user()->id)
->delete();
return $request->user();
});
3. REVOKE TOKENS AT LOGOUT
Then, to "log out" (actually, revoke tokens) the user from the frontend, you just need to call another route to revoke the token and refresh_token:
Route::middleware('auth:api')->post('/logout', function (Request $request) {
// Revoke access token
// => Set oauth_access_tokens.revoked to TRUE (t)
$request->user()->token()->revoke();
// Revoke all of the token's refresh tokens
// => Set oauth_refresh_tokens.revoked to TRUE (t)
$refreshTokenRepository = app('Laravel\Passport\RefreshTokenRepository');
$refreshTokenRepository->revokeRefreshTokensByAccessTokenId($request->user()->token()->id);
return;
});
You may prefer to put these two closures in the UserController.
Hope help someone:
if (Auth::check()) {
$request->user()->tokens->each(function ($token, $key) {
$token->delete();
});
}
Good Luck.
I use this in my project to logout from multiple device.
public function logout(Request $request, $devices = FALSE)
{
$this->logoutMultiple(\Auth::user(), $devices);
return response()->json([], 204);
}
private function logoutMultiple(\App\Models\User $user, $devices = FALSE)
{
$accessTokens = $user->tokens();
if ($devices == 'all') {
} else if ($devices == 'other') {
$accessTokens->where('id', '!=', $user->token()->id);
} else {
$accessTokens->where('id', '=', $user->token()->id);
}
$accessTokens = $accessTokens->get();
foreach ($accessTokens as $accessToken) {
$refreshToken = \DB::table('oauth_refresh_tokens')
->where('access_token_id', $accessToken->id)
->update(['revoked' => TRUE]);
$accessToken->revoke();
}
}
Try this code to help you to logout from passport authentication.
Route::post('/logout', function(){
if (Auth::check()) {
Auth::user()->AauthAcessToken()->delete();
}
return response()->json([
'status' => 1,
'message' => 'User Logout',
], 200);
});
check whether your model contains OauthAccessToken which needs to connect with the database oauth_access_tokens. The access token is stored in the database table oauth_access_tokens. and makes a relation from users to oauth_access_tokens.
public function AauthAcessToken(){
return $this->hasMany(OauthAccessToken::class);
}
You can use following code to remove to token for logged in user.
$request->user()->token()->revoke();
If you want to learn about this in-depth then watch this tutorial:
https://www.youtube.com/watch?v=UKSQdg1uPbQ
public function logout(Request $request)
{
$request->user()->token()->revoke();
if ($request->everywhere) {
foreach ($request->user()->tokens()->whereRevoked(0)->get() as $token) {
$token->revoke();
}
}
return response()->json(['message' => 'success']);
}

Resources