Laravel - Setting session variables when using Basic Auth - laravel

I'm implementing an API so that clients can access our system. I'm currently using Auth Basic for authentication. Is there a way for me to set additional session variables when they authenticate this way?
There are session variables we usually set when they log in from the login page, and so there is functionality that depends on those values. So I'll need a way to set those same variables, but when using the Auth Basic middleware.

You can write your own basic auth controllers and functions as described in the laravel documentation
Your authentication controller would look something like this:
<?php
namespace App\Http\Controllers;
use Auth;
use Illuminate\Routing\Controller;
class AuthController extends Controller
{
/**
* Handle an authentication attempt.
*
* #return Response
*/
public function authenticate()
{
if (Auth::attempt(['email' => $email, 'password' => $password])) {
// Authentication passed...
return redirect()->intended('dashboard');
}
}
}

Related

Laravel Authorization can always return false for API

I am trying to use Laravel authorization policies with API and Sanctum. However, I use middleware on the route as follows.
Route::get('/user/orders/{order}',
[OrderController::class, 'get_user_order_detail'])
->middleware('can:view:order');
OrderPolicy.php
namespace App\Policies;
use App\Models\Order;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class OrderPolicy
{
use HandlesAuthorization;
/**
* Create a new policy instance.
*
* #return void
*/
public function __construct()
{
// dd(1);
}
public function view(User $user, Order $order): bool
{
return $user->id === $order->user_id;
}
}
As you see, when I add dd(1) inside the constructor of the OrderPolicy, then I get 1 as expected, but when I move it to the inside of view function, I get unauthorized which indicates that is maybe the view function itself is not being called, but, the OrderPolicy is getting called.
Your middleware definition is wrong:
->middleware('can:view:order')
it should be:
->middleware('can:view,order')
From the docs:
Laravel includes a middleware that can authorize actions before the incoming request even reaches your routes or controllers. By default, the Illuminate\Auth\Middleware\Authorize middleware is assigned the can key in your App\Http\Kernel class. Let's explore an example of using the can middleware to authorize that a user can update a post:
use App\Models\Post;
Route::put('/post/{post}', function (Post $post) {
// The current user may update the post...
})->middleware('can:update,post');
In this example, we're passing the can middleware two arguments. The
first is the name of the action we wish to authorize and the second is
the route parameter we wish to pass to the policy method. In this
case, since we are using implicit model binding, a App\Models\Post
model will be passed to the policy method. If the user is not
authorized to perform the given action, an HTTP response with a 403
status code will be returned by the middleware.

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.

Laravel Passport Set Session

Getting below error when I am trying to set session by laravel passport
"message": "Session store not set on request.",
"exception": "RuntimeException",
This can be done in several ways.
1. Add StartSession middleware after auth:api.
This is the most straightforward solution. Add the following three lines right after auth:api.
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartSession::class,
Then remove \Illuminate\View\Middleware\ShareErrorsFromSession::class from
middlewarePriority protected property. Without this StartSession middleware will get control before auth and, more importantly, before EncryptCookies middlewares which, basically, will always lead to a new session.
<?php
namespace App\Http;
class Kernel extends HttpKernel
{
// Copy this values from
// \Illuminate\Foundation\Http\Kernel::$middlewarePriority
// then remove or comment line with StartSession. Without it,
// StartSession middleware will get control right before Auth.
// Which, basically, will create a new session because at this
// time cookies are still encrypted.
protected $middlewarePriority = [
// \Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Illuminate\Auth\Middleware\Authenticate::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Auth\Middleware\Authorize::class,
];
/**
* The application's route middleware groups.
*
* #var array
*/
protected $middlewareGroups = [
'web' => [
...
],
'api' => [
'throttle:120,1',
'bindings',
'auth:api', // https://laravel.com/docs/5.7/passport#protecting-routes
// Add the following three middleware right after `auth`
// in order to have a session.
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartSession::class,
],
];
}
2. Create your own StartSession middleware.
Having your own middleware for starting session will free you from necessaty to override middlewarePriority.
First, create a new class.
<?php
namespace App\Http\Middleware;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Session\Middleware\StartSession;
use Illuminate\Session\SessionManager;
class StartSessionShared extends StartSession
{
public function __construct(Application $app, SessionManager $manager)
{
parent::__construct($manager);
$app->singleton(StartSessionShared::class);
}
}
Then add the following three lines right after auth:api.
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartSessionShared::class,
One important note in this method is call to $app->singleton. Without it Laravel will always create a new instance of this class. Which will lead \Illuminate\Session\Middleware\StartSession::terminate method to skip saving session.
3. Create your own StartSessionReadonly middleware.
This is a good choice if you want just to share session from web
guard to api guard and don't intend to alter its values in any way. This was my case.
Create the following StartSessionReadonly middleware. Then use it in api guard instead of StartSession and two its friends.
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Contracts\Encryption\Encrypter;
use Illuminate\Contracts\Session\Session;
use Illuminate\Http\Request;
use Illuminate\Session\Middleware\StartSession;
use Illuminate\Session\SessionManager;
/**
* Middleware for sharing session between `web` and `api` guards.
* Since the latter is essentially stateless, the session from
* `web` is shared as readonly.
*
* #package App\Http\Middleware
*/
class StartSessionReadonly extends StartSession
{
protected $encrypter;
public function __construct(Encrypter $encrypter, SessionManager $manager)
{
parent::__construct($manager);
$this->encrypter = $encrypter;
}
public function handle($request, Closure $next)
{
// If a session driver has been configured, we will need to start the session here
// so that the data is ready for an application. Note that the Laravel sessions
// do not make use of PHP "native" sessions in any way since they are crappy.
if ($this->sessionConfigured()) {
$request->setLaravelSession($this->startSession($request));
}
return $next($request);
}
public function getSession(Request $request)
{
return tap($this->manager->driver(), function (Session $session) use ($request) {
$payload = $request->cookies->get($session->getName());
$unserialize = EncryptCookies::serialized($session->getName());
try {
$session->setId($this->encrypter->decrypt($payload, $unserialize));
}
catch (DecryptException $exception) {
}
});
}
}
After updating app/Http/Kernel.php you will have readonly session for all of your api.
<?php
namespace App\Http;
class Kernel extends HttpKernel
{
[...]
/****
* The application's route middleware groups.
*
* #var array
*/
protected $middlewareGroups = [
[...]
'api' => [
'throttle:120,1',
'bindings',
'auth:api', // https://laravel.com/docs/5.7/passport#protecting-routes
StartSessionReadonly::class,
],
];
Laravel Passport is a token-based authentication package for laravel
APIs typically use tokens to authenticate users and do not maintain
session state between requests. Laravel makes API authentication a
breeze using Laravel Passport, which provides a full OAuth2 server
implementation for your Laravel application in a matter of minutes.
Almost all the token-based systems including oAuth2 are by default stateless, which means there is no session attached to it,
Which means there is no session store.You can only rely on the token which is supplied on each request to validate the users identity.
That's why you are not able to set session when using laravel passport

Laravel BroadcastServiceProvider - conditional middleware for auth guard according to route request

I want to implement multiple auth guard for the broadcasting routes.
One specific guard is listening on one specific route and this is the reason why I was thinking to use the path request to define which auth guard should be used.
The implementation below doesn't work. I don't get any error message. No feedback at all. The private channel doesn't work.
If I pass the string 'auth:admin' instead of the $guard variable, it works.
When I dd the attributes from the broadcastmanager, the result seems the same if I pass the string or the variable.
Any idea what I am doing wrong?
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Broadcast;
class BroadcastServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
$guard = \Request::path() === 'home' ? 'auth:admin' : 'auth:master';
Broadcast::routes(['middleware' => ['web', $guard]]);
require base_path('routes/channels.php');
}
}
Simply provide multiple guards to the auth middleware so it will know which guard using for authentication:
Broadcast::routes(['middleware' => ['web', 'auth:admin, master']]);

Laravel Passport, multiple connexions via password client

I am having troubles understanding how could I implement multiple connexions for same user via password client with Laravel Passport:
I have a mobile app, that needs to communicate with a Laravel based API. My users, at first launch of the app, will have to enter their login and password to get an access_token.
So I think that I need to put my password client secret in the code of my mobile app to be able to request tokens. But what if, my user has an iPhone and an iPad and he wants to login from both.
I'm asking because every time I make a request to POST /oauth/token, from the same password_client, every access_token of a certain user requested with my password_client gets revoked.
That would mean that, every time my user would use his iPad, he would be disconnected from his iPhone because the token wouldn't be valid anymore?
Am I missing something?
I believe that Passport changed the way it was dealing with the Access Token creation and the method #issueToken in AccessTokenController does no longer revoke old tokens (check Multiple Access Token).
I think that this change was introduced before #jesús-lozano-m answer and therefore there is no longer the need to customize controller.
However, if you want to revoke old tokens, now you can do that by setting a listener to the Passport Event AccessTokenCreated.
app/Providers/eventServiceProvider.php:
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Event;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider {
protected $listen = [
'Laravel\Passport\Events\AccessTokenCreated' => [
'App\Listeners\RevokeOldTokens'
]
];
public function boot() {
parent::boot();
}
}
app/Listeners/RevokeOldTokens.php:
<?php
namespace App\Listeners;
use Laravel\Passport\Events\AccessTokenCreated;
use Laravel\Passport\Client;
use Carbon\Carbon;
class RevokeOldTokens {
public function __construct() {
//
}
public function handle(AccessTokenCreated $event) {
$client = Client::find($event->clientId);
// delete this client tokens created before one day ago:
$client->tokens()
->where('user_id', $event->userId)
->where('created_at', '<', Carbon::now()->subDay())
->delete();
}
}
You can write your own Controller and Routes...
Passport has a defined "Laravel\Passport\Http\Controllers\AccessTokenController" and there is a method called "issueToken()".
If you see the method below it calls the function "revokeOtherAccessTokens()", and this deletes or revoke all "access_tokens" using the "Laravel\Passport\TokenRepository"
So what you can do is to write your own controller and prevent to call "revokeOtherAccessTokens()"
The fact that you must keep in mind is that the access tokens will never be pruned or revoked at least that the refresh token became issued or manually delete them.
Refresh tokens and access tokens are revoked when refresh token is issued, because the "League\OAuth2\Server\Grant\RefreshTokenGrant" in method "respondToAccessTokenRequest()", it already revoke old "access_token" and "refresh_token", so we don't have to worry about revoke or delete them in this case.
...
// Expire old tokens
$this->accessTokenRepository->revokeAccessToken($oldRefreshToken['access_token_id']);
$this->refreshTokenRepository->revokeRefreshToken($oldRefreshToken['refresh_token_id']);
...
Here is an sample implementation, hope it helps:
routes:
Route::post('oauth/access_token', 'Auth\OAuth2Controller#issueToken');
customized controller:
<?php
namespace App\Http\Controllers\Auth;
use Laravel\Passport\Http\Controllers\HandlesOAuthErrors;
use Zend\Diactoros\Response as Psr7Response;
use Psr\Http\Message\ServerRequestInterface;
use League\OAuth2\Server\AuthorizationServer;
use App\Http\Controllers\Controller;
class OAuth2Controller extends Controller
{
use HandlesOAuthErrors;
/**
* The authorization server.
*
* #var AuthorizationServer
*/
protected $server;
/**
* Create a new controller instance.
*
* #param AuthorizationServer $server
* #return void
*/
public function __construct(AuthorizationServer $server)
{
$this->server = $server;
}
/**
* Authorize a client to access the user's account.
*
* #param ServerRequestInterface $request
* #return Response
*/
public function issueToken(ServerRequestInterface $request)
{
return $this->withErrorHandling(function () use ($request) {
return $this->server->respondToAccessTokenRequest($request, new Psr7Response);
});
}
}
I wirte below mention code in my app/Providers/AuthServiceProvider and its working. It won't removke old token and allow me login from multiple devicess
use Dusterio\LumenPassport\LumenPassport;
public function boot()
{
$this->setPassportConfiguration();
}
private function setPassportConfiguration(): void
{
LumenPassport::allowMultipleTokens();
}

Resources