When a user is not authenticated, Laravel redirects all request made to routes using auth::api middleware to the default laravel login page. However since I am building the API only, I want to return a 401 unauthorised response to any app that will be consuming my api. This should be simple but for some reason, I haven't been able to do just that. Here's what my code looks like
public function show(User $user)
{
if ($user->id == auth()->user()->id) {
// do something here
} else {
return response()->json([ 'status_message' => 'Unauthorised'], 401);
}
}
public function update(Request $request, User $user)
{
if ($user->id == auth()->user()->id) {
// do something here
} else {
return response()->json(['status_message' => 'Unathorised'], 401);
}
}
When I hit the endpoints calling these methods and the user isn't authenticated, I am redirected to the default laravel login page. How can I override this default behavior?
Call api.auth middleware in your routes as follows
$api->group(['middleware' => 'api.auth'], function ($api){
//your routes
});
Create a file Authenticate.php inside Middleware folder
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Contracts\Auth\Factory as Auth;
class Authenticate {
/**
* The authentication guard factory instance.
*
* #var \Illuminate\Contracts\Auth\Factory
*/
protected $auth;
/**
* Create a new middleware instance.
*
* #param \Illuminate\Contracts\Auth\Factory $auth
* #return void
*/
public function __construct(Auth $auth)
{
$this->auth = $auth;
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #param string|null $guard
* #return mixed
*/
public function handle($request, Closure $next, $guard = null)
{
if ($this->auth->guard($guard)->guest()) {
return response('Unauthorized.', 401);
}
return $next($request);
}
}
and in kernel.php which is inside http folder include the following
protected $routeMiddleware = [
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
];
Problem solved guys. Here's how I did it, in the Handle.php file in app\Exceptions\ I added this code to the render function
if ($exception instanceof AuthenticationException) {
return response()->json(['status_message' => 'Unauthorised'], 401);
}
And that was it.
Note: this worked in laravel 5.8
I am using laravel-echo-server to run Laravel Echo to broadcast events.
I have a user counter channel which shows all the users on the app. For this I am using a presence channel. This works fine for logged in users, but guests just never get connected.
I've setup the below in the BroadcastServiceProvider:
Broadcast::channel('global', function () {
return ['name' => 'guest'];
});
Which from what I can tell, should allow everyone in as 'guests'. I'm guessing there's some middleware or auth that's being checked before this that I need to disable for this channel.
Any help on getting all clients joining this presence channel would be much appreciated!
The other solutions didn't work for me for a guest presence channel, this is what I ended up with:
// routes/channels.php
<?php
use Illuminate\Auth\GenericUser;
/*
|--------------------------------------------------------------------------
| Broadcast Channels
|--------------------------------------------------------------------------
|
| Here you may register all of the event broadcasting channels that your
| application supports. The given channel authorization callbacks are
| used to check if an authenticated user can listen to the channel.
|
*/
Route::post('/custom/broadcast/auth/route', function () {
$user = new GenericUser(['id' => microtime()]);
request()->setUserResolver(function () use ($user) {
return $user;
});
return Broadcast::auth(request());
});
Broadcast::channel('online.{uuid}', function ($user, $uuid) {
return [
'id' => $user->id,
'uuid' => $uuid
];
});
You may create a temporary user with factory(User::class)->make(...) and authenticate it with a middleware to use it as a guest.
Step 1: Creating the middleware
Run: php artisan make:middleware AuthenticateGuest
In app/Http/Middleware/AuthenticateGuest.php:
public function handle($request, Closure $next)
{
Auth::login(factory(User::class)->make([
'id' => (int) str_replace('.', '', microtime(true))
]));
return $next($request);
}
Now setup the AuthenticateGuest middleware in Kernel.php.
In app\Http\Kernel.php:
protected $routeMiddleware = [
...
'authenticate-guest' => \App\Http\Middleware\AuthenticateGuest::class,
];
Step 2: Setup Broadcast::channel route
In routes/channels.php:
Broadcast::channel('chatroom', function ($user) {
return $user; // here will return the guest user object
});
More at: https://laravel.com/docs/8.x/broadcasting#authorizing-presence-channels
For anyone looking for answers to this. It is indeed possible to auth guests into presence channels you just need to override the Broadcast::routes() from the service provider with your own.
As an example my presence channel 'global' accepts guests:
Route::post('/broadcasting/auth', function(Illuminate\Http\Request $req) {
if($req->channel_name == 'presence-global'){return 'global';}
return abort(403);
});
This could be extended in various directions, or could continue to pass other presence and private channels through to the default Broadcast::auth method
You can create your own auth guard and it's also pretty simple but more complex.
Create a class which will implement Authenticable Interface.
Create UserProvider.
Create a new Guard.
Register Guard and UserProvider in AuthServiceProvider.
Add provider and guard in config/auth.php
Use your new guard.
Advantages
You don't have to modify auth endpoint
You don't have to change default guard
You base on Laravel Auth system
Keep support of multiple tabs in the browser
Can be used with web guard at the same time
Keep all the advantages of using PresenceChannel
Disadvantages
A lot to code
So,
1. Create a new class which will implement Authenticable interface.
<?php
namespace App\Models;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Contracts\Support\Jsonable;
use JsonSerializable;
/**
* #property string $id
* #property string $name
*/
class Session implements Authenticatable, Jsonable, Arrayable, JsonSerializable
{
private $id;
private $attributes = [];
public function __construct($id)
{
$this->id = $id;
$this->name = "Guest";
}
/**
* Get the name of the unique identifier for the user.
*
* #return string
*/
public function getAuthIdentifierName()
{
return 'id';
}
/**
* Get the unique identifier for the user.
*
* #return mixed
*/
public function getAuthIdentifier()
{
return $this->{$this->getAuthIdentifierName()};
}
/**
* Get the password for the user.
*
* #return string
*/
public function getAuthPassword()
{
return "";
}
/**
* Get the token value for the "remember me" session.
*
* #return string
*/
public function getRememberToken()
{
return $this->{$this->getAuthIdentifierName()};
}
/**
* Set the token value for the "remember me" session.
*
* #param string $value
* #return void
*/
public function setRememberToken($value)
{
$this->{$this->getRememberToken()} = $value;
}
/**
* Get the column name for the "remember me" token.
*
* #return string
*/
public function getRememberTokenName()
{
return "token";
}
public function __get($name)
{
return $this->attributes[$name];
}
public function __set($name, $value)
{
$this->attributes[$name] = $value;
}
/**
* Convert the object to its JSON representation.
*
* #param int $options
* #return string
*/
public function toJson($options = 0)
{
return json_encode($this);
}
/**
* Get the instance as an array.
*
* #return array
*/
public function toArray()
{
return $this->attributes;
}
/**
* Specify data which should be serialized to JSON
* #link https://php.net/manual/en/jsonserializable.jsonserialize.php
* #return mixed data which can be serialized by <b>json_encode</b>,
* which is a value of any type other than a resource.
* #since 5.4.0
*/
public function jsonSerialize()
{
return $this->attributes;
}
}
Modify this as you wish, but you shouldn't serialize $id property
2. Create UserProvider
<?php namespace App\Extensions;
use App\Models\Session;
use Illuminate\Cache\Repository;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Support\Fluent;
use Illuminate\Support\Str;
class SessionUserProvider implements UserProvider
{
private $store;
/**
* SessionUserProvider constructor.
* #param Repository $store
*/
public function __construct(Repository $store)
{
$this->store = $store;
}
/**
* Retrieve a user by their unique identifier.
*
* #param mixed $identifier
* #return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveById($identifier)
{
return new Session(
$this->getUniqueTokenForSession($identifier)
);
}
/**
* Retrieve a user by their unique identifier and "remember me" token.
*
* #param mixed $identifier
* #param string $token
* #return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveByToken($identifier, $token)
{
return null;
}
/**
* Update the "remember me" token for the given user in storage.
*
* #param \Illuminate\Contracts\Auth\Authenticatable $user
* #param string $token
* #return void
*/
public function updateRememberToken(Authenticatable $user, $token)
{
return;
}
/**
* Retrieve a user by the given credentials.
*
* #param array $credentials
* #return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveByCredentials(array $credentials)
{
return null;
}
private function unpack($data)
{
return json_decode($data);
}
private function getUniqueTokenForSession($id)
{
return $this->retrieveCacheDataForSession($id)
->get('uuid');
}
private function retrieveCacheDataForSession($id)
{
$fluent = new Fluent(
$this->unpack(
$this->store->has($id) ? $this->store->get($id) : "[]"
)
);
if(!$fluent->__isset('uuid')) {
$fluent->__set('uuid', Str::random(128));
}
$this->store->put($id, $fluent->toJson(), 60 * 60 * 60);
return $fluent;
}
/**
* Validate a user against the given credentials.
*
* #param \Illuminate\Contracts\Auth\Authenticatable $user
* #param array $credentials
* #return bool
*/
public function validateCredentials(Authenticatable $user, array $credentials)
{
return null;
}
}
Identifier property in retrieveById method is always session id if you are using broadcasting so you can also use this as a token.
3. Create new Guard
<?php namespace App\Services\Auth;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Http\Request;
class GuestGuard implements Guard
{
private $user;
protected $request;
protected $provider;
/**
* GuestGuard constructor.
* #param UserProvider $provider
* #param Request $request
*/
public function __construct(UserProvider $provider, Request $request)
{
$this->provider = $provider;
$this->request = $request;
}
/**
* Determine if the current user is authenticated.
*
* #return bool
*/
public function check()
{
return !is_null($this->user);
}
/**
* Determine if the current user is a guest.
*
* #return bool
*/
public function guest()
{
return !$this->check();
}
/**
* Get the currently authenticated user.
*
* #return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function user()
{
if($this->check()) {
return $this->user;
}
$this->setUser(
$this->provider->retrieveById(
$this->request->session()->getId()
)
);
return $this->user;
}
/**
* Get the ID for the currently authenticated user.
*
* #return int|null
*/
public function id()
{
return !is_null($this->user) ? $this->user->id : null;
}
/**
* Validate a user's credentials.
*
* #param array $credentials
* #return bool
*/
public function validate(array $credentials = [])
{
return false;
}
/**
* Set the current user.
*
* #param \Illuminate\Contracts\Auth\Authenticatable $user
* #return void
*/
public function setUser(Authenticatable $user)
{
$this->user = $user;
}
}
Here in user method you pass session id as identifier, using broadcasting only this method is nessesary.
4. Register Guard and UserProvider in AuthServiceProvider.
// app/Providers/AuthServiceProvider.php
/**
* Register any authentication / authorization services.
*
* #return void
*/
public function boot()
{
$this->registerPolicies();
Auth::provider('sessions', function (Application $app) {
return new SessionUserProvider(
$app->make('cache.store')
);
});
Auth::extend('guest', function (Application $app, $name, array $config) {
return new GuestGuard(Auth::createUserProvider($config['provider']), $app->make('request'));
});
}
5.1 Add provider in config/auth.php
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
// New
'sessions' => [
'driver' => 'sessions',
'model' => App\Models\Session::class,
],
],
5.2 Add guard in config/auth.php
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => false,
],
// New
'guest' => [
'driver' => 'guest',
'provider' => 'sessions'
]
],
6. Use your new guard
// routes/channels.php
Broadcast::channel('chat.{id}', function (Authenticatable $user){
return $user;
}, ['guards' => ['guest']]);
Notice that you can use 'web' as a guard at the same time ('web' should be before 'guest'). It allows you to find out who is a guest and who is a logged in user - you can just check instance of Authenticable in channel callback.
And that how it looks in the laravel-echo-server database
With the help of Renan Coelho i got it to work. The missing part for me was to override the Broadcast::routes() method with the following:
Route::post('/broadcasting/auth', function (Illuminate\Http\Request $req) {
return Broadcast::auth($req);
});
Route::post('/broadcasting/auth'... is actually a route that gets added through the "Broadcast::routes()" method. This is why we override it here. You can see the active routes by typing php artisan route:list in your terminal.
Then, Renan Coelho already said, i had to add a custom Middleware (AuthenticateGuest) that creates a random user for me. (This is the hacky part) and add it to the $middleware array in the kernel.php:
protected $middleware = [
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\TrustProxies::class,
\Barryvdh\Cors\HandleCors::class,
\App\Http\Middleware\AuthenticateGuest::class
];
The AuthenticateGuest Middleware looks like the following:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
use App\User;
class AuthenticateGuest
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
Auth::login(factory(User::class)->make([
'id' => (int)str_replace('.', '', microtime(true))
]));
return $next($request);
}
}
Hope that helps someone,
Sebastian
My solution to issue:
BroadcastServiceProvider.php (~/app/Providers/)
public function boot()
{
if (request()->hasHeader('V-Auth')) { /* Virtual client. */
Broadcast::routes(['middleware' => 'client_chat.broadcast.auth']);
} else {
Broadcast::routes();
}
require base_path('routes/channels.php');
}
Kernel.php (~/app/Http/)
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
...
'client_chat.broadcast.auth' => \App\Http\Middleware\ClientChatBroadcasting::class,
];
ClientChatBroadcasting.php (~/app/Http/Middleware/)
public function handle($request, Closure $next)
{
if (/** your condition **/) {
$fakeUser = new User;
$fakeUser->virtual_client = true;
$fakeUser->id = /** whatever you want **/;
$fakeUser->name = '[virtual_client]';
$fakeUser->asdasdasdasdasd = 'asdasdasdasdasd';
$request->merge(['user' => $fakeUser]);
$request->setUserResolver(function () use ($fakeUser) {
return $fakeUser;
});
}
return $next($request);
}
ChatChannel.php (~/app/Broadcasting/Chat/)
Broadcast::channel('chat.{chatId}', ChatChannel::class); Channel Classes
public function join($member/**($fakeUser)**/, $chatId)
{
$memberData = [/** your data **/];
/* If there is no member data (null), then there will be an authentication error. */
return $memberData;
}
[place in your js file, where you want connect to broadcasting]
this.Echo = new Echo({
broadcaster: 'socket.io',
host: /** your host **/,
reconnectionAttempts: 60,
encrypted: true,
auth: {
headers: {
'V-Auth': true,
'Access-Token': accessToken,
'Virtual-Id': virtualId,
'Chat-Id': chatId
}
}
});
I made it here reading this blog, getting the /broadcasting/auth error. The answers were very helpful but I didn't find the complete answer I needed.
I did this in Laravel 9.x so FYI it will work in that version too.
The major missing piece of code I needed was in the routes/web.php:
use Illuminate\Support\Facades\Route;
Broadcast::routes([
'middleware' => ['web', 'authenticate-guest'],
]);
Route::get('/', function () {
return view('welcome');
});
However I will add the rest down here in case anyone needs the whole thing:
Remove comment in app.config provider:
App\Providers\BroadcastServiceProvider::class,
Make middleware: php artisan make:middleware AuthenticateGuest.php
Then update it, in app/Http/Middleware
public function handle(Request $request, Closure $next)
{
Auth::login(User::factory()->make([
'id' => (int) str_replace('.', '', microtime(true))
]));
return $next($request);
}
app/Http/Kernel.php File..
protected $routeMiddleware = [
...
'authenticate-guest' => \App\Http\Middleware\AuthenticateGuest::class
];
app/Providers/BroadcastServiceProvider.php file should be this, but check:
public function boot()
{
Broadcast::routes();
require base_path('routes/channels.php');
}
Lastly, the routes/channel.php file:
use Illuminate\Support\Facades\Broadcast;
Broadcast::channel('common_room', function ($user) {
return true;
});
To finish everything off, do a npm run dev, followed by php artisan route:clear and maybe php artisan cache:clear/php artisan config:clear as well. Hope that helps!
I don't think that my validator extentions are working, but I can't fathom why.
Service Provider.
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
Validator::extend('fail', function ($attribute, $value, $parameters, $validator) {
return false;
});
}
/**
* Register any application services.
*
* #return void
*/
public function register()
{
}
}
I know the Service Provider's boot is firing because if I dd(); inside the boot method I get output. If I add a dd(); to the extend closure function, I do not get any output.
Request
class SaveOrder extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
$rules = [
'customer_id' => 'in:' . Customer::getImplodedCurrentTeamKeys(),
'pin' => 'fail'
];
return $rules;
}
}
I know the request is validating correctly because if I change the rule to 'pin' => 'required' and don't give in put I get a fail.
Why is my custom validation rule not working?
I found my solution at the very bottom of the Laravel Validation docs page: (https://laravel.com/docs/5.4/validation)
For a rule to run even when an attribute is empty, the rule must imply that the attribute is required. To create such an "implicit" extension, use the Validator::extendImplicit() method:
By changing the method from extend to extendImplicit , my problem was solved.
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
Validator::extendImplicit('fail', function ($attribute, $value, $parameters, $validator) {
return false;
});
}
/**
* Register any application services.
*
* #return void
*/
public function register()
{
}
}
I've got a middleware setup that's caching the HTML output of each public Controller request, in each of my controllers' __construct method.
public function __construct() {
$this->middleware('auth', ['except' => ['index', 'show']]);
$this->middleware('cache.get', ['only' => 'show']);
$this->middleware('cache.put', ['only' => 'show']);
}
The caching is working great, as expected, except for one thing: I've got Route-Model bindings setup in RouteServiceProvider.php for easy accessiblity of models in their respective controllers, like
public function boot(Router $router)
{
parent::boot($router);
$router->bind('posts', function($id) {
return \App\Article::findBySlugOrIdOrFail($id);
});
$router->bind('tags', function($name) {
return \App\Tag::where('name', $name)->firstOrFail();
});
$router->bind('artists', function($slug) {
return \App\Artist::findBySlugOrIdOrFail($slug);
});
Basically what's happening is even when pages are cached, I'm still getting a single query for each route where it's looking up that slug (SluggableInterface) or id. Wondering if there's a way to do this where the query doesn't occur when a route is cached? Or is that just not possible?
Thanks!
EDIT
Here's my caching middleware:
class GetCache
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$key = StringHelper::keygen($request->path());
if (Cache::has($key) && Auth::guest()) {
$content=Cache::get($key);
return response($content);
}
return $next($request);
}
}
class PutCache
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$response = $next($request);
$key = StringHelper::keygen($request->path());
if (! Cache::has($key) && Auth::guest()) {
Cache::put($key, $response->getContent(), 600);
}
return $next($request);
}
}
I'm having a trouble with creating the "owner" middleware.
For example, I have a Articles and Usermodel associated with user_id key.
I want to add the "owner" middleware to the ArticlesController, so the only owner of that article can edit, update and delete it.
I've been searching for this issue for a while, but never found the code, which would work.
Some of them tried to make it work with Form Requests, but I'm interested in using Middleware.
Create middleware:
php artisan make:middleware OwnerMiddleware
namespace App\Http\Middleware;
use App\Article;
use Closure;
use Illuminate\Contracts\Auth\Guard;
class OwnerMiddleware
{
/**
* The Guard implementation.
*
* #var Guard
*/
protected $auth;
/**
* Create a new filter instance.
*
* #param Guard $auth
* #return void
*/
public function __construct(Guard $auth)
{
$this->auth = $auth;
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$articleId = $request->segments()[1];
$article = Article::findOrFail($articleId);
if ($article->user_id !== $this->auth->getUser()->id) {
abort(403, 'Unauthorized action.');
}
return $next($request);
}
}
Add it to app\Http\Kernel.php:
protected $routeMiddleware = [
'owner' => 'App\Http\Middleware\OwnerMiddleware',
];
Use middleware in your routes:
Route::group(['middleware' => ['owner']], function() {
// your route
});
Alternatively you could use route and middleware parameters, it has some advantages:
Even if the request structure changes your middleware would still work
The middleware is reusable for differents resources
You can use it inside controllers
Here’s the middleware (app/Http/Middleware/AbortIfNotOwner.php):
<?php
namespace App\Http\Middleware;
use Closure;
class AbortIfNotOwner
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #param string $resourceName
* #return mixed
*/
public function handle($request, Closure $next, $resourceName)
{
$resourceId = $request->route()->parameter($resourceName);
$user_id = \DB::table($resourceName)->find($resourceId)->user_id;
if ($request->user()->id != $user_id) {
abort(403, 'Unauthorized action.');
}
return $next($request);
}
}
Inside app\Http\Kernel.php:
protected $routeMiddleware = [
'owner' => 'App\Http\Middleware\AbortIfNotOwner',
];
Inside your route file (app/Http/routes.php):
Route::group(['middleware' => ['owner:articles']], function() {
// your route
});
And optionally call it in the controller:
public function __construct()
{
$this->middleware('owner:articles', ['only' => ['edit', 'update']]);
}