Laravel 8 - Can't set cookies in middleware - laravel

I'm building a shopping cart SPA using Laravel and Vue.
Vue communicates with Laravel via the API, which is not a stateless as I'm using Sanctum for a cookie based session authentication services.
For guest users I want to store the cart info in a cookie. So I'm trying to set a cookie via a middleware I created with the following handle() method:
public function handle(Request $request, Closure $next)
{
if(!$request->cookie('cart_id')){
$cart_id = "some value";
return $next($request)->cookie(cookie()->forever('cart_id', $cart_id));
}
return $next($request);
}
I added this middleware to both 'api' and 'web' groups, but it doesn't seem to add the cookie no matter how many page requests I make.
Would appreciate some help with this Thanks

Related

Auth::check fails in middleware

Can somebody explain why this strange behavior of Laravel is happening? Basically, I am trying to create a middleware for my application
public function handle(Request $request, Closure $next)
{
if (auth()->check()) {
$expires = Carbon::now()->addMinute(2);
\Illuminate\Support\Facades\Cache::put('user-is-online-' . Auth::user()->id, true, $expires);
}
return $next($request);
}
}
But auth()->check it is keep failing and not returning true , (user is authenticated) , auth()->check is working in other places like web routes and controllers method but why not here ?
If you are using auth middleware to protect your routes, then make sure this auth middleware is set to run before your middleware, otherwise auth()->check() will return false. Try php artisan route:list and check the orders of middlewares for your specified route.

Laravel 8: Using Fortify in APIs

Can Laravel Fortify be used in the context of API? From what I understand, Fortify (although being headless, i.e. doesn't include a UI layer) allows us to customize Login and Register pages, but it automatically redirects to HOME page upon successful authentication. And although the HOME page is customizable, that is not how API logins normally work. It should simply return a success token in JSON format without redirects of any kind.
There is an authenticateUsing function in Fortify, but even that simply allows us to customize authentication logic and not the returned data. Redirection is still performed by Fortify.
How can I use Fortify in the context of REST API?
Note: I'm going to use it from my Vue front-end application. I'm also going to get Sanctum into the game, but before that I just wanted to see if I can do regular token-based authentication using Fortify without having to write my own login, register and logout routes and controller functions.
Authentication can either be Session-based or Token-based.
Laravel Fortify only provides the backend logic nessecery for session-based authentication and therefore is not intended for token-based API authentication.
If you need token-based API authentication, you can use either Sanctum or Passport depending on your needs. But You'll have to write a bit of code, in either case.
If you decide to go with Laravel Passport, I have a boilerplate project that might be of use: https://github.com/pktharindu/laravel-api-boilerplate-passport
Just set 'Accept' header with 'application/json' or 'application/javascript' then fortify will response json formatted body not redirection.
by the way, use Sanctum instead of Passport for SPA is easier to keep token securely. google about where to store API token for SPA then you will find out why.
The redirects reason in my case was the default laravel RedirectIfAuthenticated provider.
By default in laravel 8 that provider looks like
<?php
namespace App\Http\Middleware;
use App\Providers\RouteServiceProvider;
use Closure;
use Illuminate\Support\Facades\Auth;
class RedirectIfAuthenticated
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #param string[]|null ...$guards
* #return mixed
*/
public function handle($request, Closure $next, ...$guards)
{
$guards = empty($guards) ? [null] : $guards;
foreach ($guards as $guard) {
if (Auth::guard($guard)->check()) {
return redirect(RouteServiceProvider::HOME);
}
}
return $next($request);
}
}
just replace default foreach by the following code with that:
foreach ($guards as $guard) {
if (Auth::guard($guard)->check()) {
if ($request->expectsJson()) {
return response()->json([
'error' => 'Already authenticated.'
], 406);
}
return redirect(RouteServiceProvider::HOME);
}
}
Don't forget that for $request->expectsJson() to work fine you should include Accept: application/json in the request headers.

Auth::user() is null in new route

i'm using laravel 6 and have 2 route in my app; index and dashboard.
My routes/web is:
Auth::routes();
Route::middleware(['auth'])->group(function () {
Route::get('/index', 'todoApp\TodoController#index')->name('index');
Route::get('/dashboard', 'todoApp\Dashboard#dashboard')->name('dashboard');
});
i added dashboard route recently.
Auth::user() is null when i dump it in dashboard route but doesn't in index. What's the
Your Controller is instantiated before the middleware stack has ran; this is how Laravel can know what middleware you have set via the constructor. Because of this you will not have access to the authenticated user or sessions at this point. Ex:
public function __construct()
{
$this->user = Auth::user(); // will always be null
}
If you need to assign such a variable or access this type of information you would need to use a controller middleware which will run in the stack after the StartSession middleware:
public function __construct()
{
$this->middleware(function ($request, $next) {
// this is getting executed later after the other middleware has ran
$this->user = Auth::user();
return $next($request);
});
}
When the dashboard method is called, the middleware stack has already passed the Request all the way through to the end of the stack so all the middleware needed for Auth to be functioning and available has already ran at that point which is why you have access to Auth::user() there.
I think that this has something to do with the 'web' middleware. If you take a look into the Kernel.php (In app\Http) you will find the web middleware group.
This will show you that it actually calls a middleware called StartSession. Based on your route file (where web is not included as a middleware) I would think that you don't have a session in your Controller and there for no access to it.
I don't quite understand why this only happens in your /dashboard route, because the issue should also be in your /index route (unless you added the web middleware somewhere in your TodoController).
I think that this should do the trick:
Route::middleware(['web', 'auth'])->group(function () {
Route::get('/index', 'todoApp\TodoController#index')->name('index');
Route::get('/dashboard', 'todoApp\Dashboard#dashboard')->name('dashboard');
});
If you fire php artisan make:auth command.
It's doesn't matter where you define because of it's only define auth route
Route::middleware(['auth'])->group(function () {
Route::get('/index', 'todoApp\TodoController#index')->name('index');
Route::get('/dashboard', 'todoApp\Dashboard#dashboard')->name('dashboard');
});
Auth::routes();

Laravel rest API returns user object in the post request

I created an API for login and register with the passport package in Laravel. When I call the login url in Postman it returns key_token, refresh token and expired_date.
But I want also want to return the authorized user info in json (eg the username and pass and email).
I need this because the mobile phone team wants to integrate my web app.
Login controller method:
public function login(Request $request)
{
$this->validate($request,[
'username'=>'required',
'password'=>'required'
]);
$params = [
'grant_type'=>'password',
'client_id'=>$this->client->id,
'client_secret'=>$this->client->secret,
'username'=>request('username'),
'password'=>request('password'),
'scope'=>'*'
];
$request->request->add($params);
$proxy=Request::create('oauth/token','POST');
return Route::dispatch($proxy);
}
In my opinion, the best way to go is to create another URI that returns the authenticated user. This way, the client signs in and then performs a GET request to get the authenticated user. I like to simply set this URI to /user. Laravel side, you just have to create a route like this:
Route::get('/user', function (Request $request) {
return $request->user();
})->middleware('auth:api');
By the way, this is how the GitHub API works.
Hope that helps.

How to have a route for both authenticated users and non-authenticated users

I have an issue with auth:api middleware!
We have a request that is accessible for both authenticated users and non-authenticated users when I define a route like this for non-authenticated users:
Route::post('{user}/leads', 'UsersController#getContact');
It's ok everything work fine when a guest user requesting this route.
is and I can access user with $request->user();
but if pass token with bearer header and get the user with $request->user() of course it doesn't work! because we didn't use auth:api on this route, and if we do we can't access this route with guest users!
So I can't find a way that we define one route for both authenticated users that if user is authenticated we get $request->user() and none authenticated users can access that route too!
Thanks in advance.
I found a way to do that I just wrote this:
$middleware = ['api'];
if (\Request::header('Authorization'))
$middleware = array_merge(['auth:api']);
Route::group(['prefix' => 'v1', 'namespace' => 'Api', 'middleware' => $middleware], function () {
//routes here
});
In api.php route file and it works fine.
Thanks
This is because Auth uses the default web guard. You have to check the api guard manually:
$user = Auth::user() ?? Auth::guard("api")->user();
Then you don't use any auth middleware. $user will be null if the user is a guest, otherwise it should be set.
The solution I used was to create a new middleware for auth:
public function handle($request, Closure $next, ...$guards)
{
try
{
$this->authenticate($request, $guards);
}
catch(AuthenticationException $ex)
{
}
return $next($request);
}
and in at the BOTTOM of my route I did:
Route::middleware('auth_optional:api')->group(function () {
Route::get('services', [ServiceController::class,'index']);
});
This way if Auth was needed ,it would assign the correct user to request, otherwise it would proceed as guest. I did need to do a $request->user() === null to make sure the user is guest
If you want the routes are visible to only Authenticate users you can put all routes in auth middleware that is default provided by laravel you can put like this:-
enter code here
Route::group(['middleware' => ['auth']], function () {
Route::post('{user}/leads', 'UsersController#getContact');
});
And if you want to show the route both authenticate and non-authenticate user
You can simply put outside the middleware
Lik that:-
Route::match(['get', 'post'], '/cms-page','CmsController#cms');
Hope you understand
I would like to use additional routes both authenticated and non-authenticated users,
But regarding the topic I add one simple way :
On the __constructor function of the Controller add those lines :
$authorizationHeader = \request()->header('Authorization');
if(isset($authorizationHeader)) {
$this->middleware('auth:api');
}
But I don't concentrate this way as best practice, this brokes Single Responsibility Principle.
If you are using Laravel Passport then this way can be more cleaner.
In controller you can directly get user by
$user = $request->user('api');
This will get you the authenticated user and if bearer token is invalid then it will not throw 'unauthenticated' error but result in null user.
Reference: How to authenticate user without auth:api middleware in laravel 5.3?

Resources