Laravel Passport Set Session - laravel

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

Related

Is it possible to get session variables and call API in Laravel view composer?

In AppServiceProvider.php, I am trying to get data from session then call API with it then pass a variable after getting it from the response.
Also, I don't know if it's right. I added "Request $request" to boot function as in other parts of code.
And the error I'm getting is "RuntimeException in Request.php line 388: Session store not set on request." Does that mean session variable isn't set? I would've thought they'd be available after I log in to my site as I session put "token" and "member_id" during login.
Is it because view controller is higher level so my session puts during login won't come before bootstrap code in boot function here uses them? Oh, or is the request not really passed in as parameter of boot function as I would've liked it to. How would I otherwise do that or get variables from the session?
Anyway, are the steps I'm taking proper? If I'm doing things incorrectly throughout, such as bad practice, please point it out as well thanks.
Here's my code:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Http\Request;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot(Request $request)
{
$client = new \GuzzleHttp\Client();
$params = array(
'token' => $request->session()->get('token'),
'member_id' => $request->session()->get('member_id'),
'activity' => 'GET MEMBER INFO'
);
$response = $client->request('POST',
env('SPACE_4_CAR_API_DOMAIN') . 'select_api/GetMemberInfo.php',
['json' => $params]
);
$returnData = json_decode($response->getBody());
view()->composer('layout', function ($view) {
$view->with('is_admin', $returnData->is_administrator);
});
}
/**
* Register any application services.
*
* #return void
*/
public function register()
{
//
}
}

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

Share data to all views in laravel5.2

I have the following problem, I want to share an array to all views in my project so I followed the documentation and it works fine, but I want to get the authenticated user in service provider boot function and it always return null ?
any suggestions ?
this is my code
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public $myusers;
public function boot()
{
$origLat=\Auth::user()->lat;
$origLon=\Auth::user()->lng;
$dist=5;
$lon1=$origLon-$dist/cos(deg2rad($origLat))*73.2044736;
$lon2=$origLon+$dist/cos(deg2rad($origLat));
$lat1=$origLat-($dist/73.2044763);
$lat2=$origLat+($dist/73.2044763);
$id=\Auth::user()->id;
$pictures=User::find($id)->pictures;
$this->myusers = DB::table('users')->select(
DB::raw("*,
3956 * 2 *
ASIN(SQRT( POWER(SIN(($origLat- lat)*pi()/180/2),2)
+COS($origLat*pi()/180 )*COS(lat*pi()/180)
*POWER(SIN(($origLon-lng)*pi()/180/2),2)))*1.609344
as distance"
))
->where('users.id', '!=', \Auth::user()->id)
->whereBetween('lng',[$lon1,$lon2])
->whereBetween('lat',[$lat1,$lat2])
->having("distance", "<", "$dist")
->orderBy("distance")
->get();
view()->share('myusers', $this->myusers);
}
/**
* Register any application services.
*
* #return void
*/
public function register()
{
//
}
}
Unfortunately, at this point the Laravel application request lifecycle works in such a way that when the boot method of the App\Providers\AppServiceProvider class is executed the session is not yet initialised (since that's done in a middleware that is executed after the boot method).
Since the authentication systems needs the session in order to get the authenticated user, in your particular case you can't use view()->share() successfully there (although it's the recommended approach). Instead you can use an alternative approach by doing that in a middleware. Here are the steps that you can follow to make this work:
1. Create a middleware class, let's call it LoadUsers, by running this command:
php artisan make:middleware LoadUsers
2. That will generate a class in app/Http/Middleware/LoadUsers.php. Now you just need to move your code from the AppServiceProvider to the handle method of the middleware:
class LoadUsers
{
public function handle($request, Closure $next)
{
// Your code that shares the data for all views goes here
return $next($request);
}
}
3. Next you need to register the middleware with the App\Http\Kernel class. You can add it to the web group from $routeMiddleware if you want to apply the middleware to all routes that that use that or create your specific group or route middleware. So something like this if you want to add it to web:
protected $middlewareGroups = [
'web' => [
...
// Make sure to add this line is after the
// StartSession middleware in this list
\App\Http\Middleware\LoadUsers::class,
],
...
];
Now you should have the proper shared data for all your views that can depend on Auth::user().

Laravel - Setting session variables when using Basic Auth

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

Resources