Laravel Passport, multiple connexions via password client - laravel

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

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 Dusk - how do I assert who the browser is logged in as?

I have a Laravel system that allows people to authenticate by clicking a url containing a login 'token'. This facilitates convenient access on mobile devices without having to manage passwords.
I have created a model named App\LoginTokens and a web.php route:
Route::get('/arbitraryprefix/{tokenCode}','LoginTokenController#loginWithTokenAndRedirect')
This controller method validates the token, and if valid, logs the user in using \Auth::loginUsingId($loginToken->user_id) and redirects to the token's url $loginToken->url.
There is obvously other security measures (including token expiry and rules to ensure the user being authenicated is of one specific class that does not have much access in the system.
I am writing some test cases in Laravel Dusk, and want to check that the token logs the person in as the correct user.
My system in on Laravel 5.4 so I use assertPathIs as assertUrlIs in not avaialble in 5.4, and this confirms I redirect to the correct document.
$this->browse(function ($browser) use ($loginToken,$oneClientUser) {
$browser->visit('/logout')
->waitForText('Login')
->visit($loginToken->getLoginAndRedirectUrl())
->waitForText($oneClientUser->name)// the users name shows in top right when they are logged in.
->assertPathIs(parse_url($loginToken->url,PHP_URL_PATH))
->visit('/logout')
->waitForText('Login');
});
I want to chain onto the browser a call like
$browser->assertUserIs($oneClientUser)
but I have had to settle for checking for their user name, which is displayed for authenticated users.
Is there a way to verify which user I am authenticated as in a $this->browse(function($browser){ --here--}) callback?
Yes, there is: assertAuthenticatedAs()
use Tests\DuskTestCase;
use Laravel\Dusk\Browser;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use App\User;
class AuthTestExample extends DuskTestCase
{
use DatabaseMigrations;
protected $user
public function setUp(): void
{
parent::setUp();
$this->user = factory(User::class)->create(['password' => bcrypt('password')]);
}
public function tearDown(): void
{
$this->browse(function (Browser $browser) { $browser->logout(); });
parent::tearDown();
}
public function testLogin()
{
$this->browse(function (Browser $browser) {
$browser->assertGuest()
->visit('/login')
->type('#login', $this->user->login)
->type('#password', 'password')
->click('#login-button')
->assertPathIs('/dashboard')
->assertAuthenticatedAs($this->user);
});
}
}

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

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