So, as my title says, I want to change Laravel's default Broadcast auth middleware to a custom auth middleware that I made which uses token-based authentication. I made this because my app is an API-based app, and, when a user authenticates, I create a session token and send it to him and also store it inside the DB with an expires_at column.
I am using Pusher.
I have the following middleware:
class AuthCustom
{
public function handle($request, Closure $next)
{
// if we have the session token stored in header
if ($request->header('x-session')) {
$session = Session::where('id', $request->header('x-session'))->where('expires_on', '>=', date('Y-m-d G:i:s'))->with('user')->first();
if ($session !== null) {
$user = (new User())->where('id', $session->user_id)->first();
if ($user !== null) {
$request->merge(['user' => $user]);
return $next($request);
}
}
}
}
My BroadcastServiceProvider code is as follows:
class BroadcastServiceProvider extends ServiceProvider
{
public function boot()
{
Broadcast::routes();
require base_path('routes/channels.php');
}
}
If I put Broadcast::routes(['middleware' => 'authcustom']); in BroadcastServiceProvider, the boradcasting/auth gives a 403 status code because $request->user() is null, which then results in an Access forbidden.
I have tried searching the whole damn web, and I found nothing about changing the default auth middleware for broadcasting.
I even tried removing Broadcast::routes() and customizing a new route /broadcast which returned a Pusher socket_auth object and everytime I got a 419 Unkown status code.
Any ideas or maybe you can point me in the direction where I could manage this?
Thank you!
Later edit:
My JS Echo connection looks like this:
Vue.use(VueEcho, {
broadcaster: 'pusher',
key: 'xxxxxxxxxxxxxx',
cluster: 'eu',
authEndpoint: 'http://localhost/api.easycargo.ro/public/broadcasting/auth',
auth: {
headers: {
'x-session': this.auth.token
}
}
});
I'm glad you got something working. For later readers, here's a more Laravel-esque way to solve the problem in the question: create a custom auth guard used to authenticate requests for the special routes.
Laravel's AuthManager includes a helper method—viaRequest()—that simplifies the creation of a Guard that authenticates a user with data from the request context without the need to fully-implement Illuminate\Contracts\Auth\Guard. We can bind our custom guard in the boot() method in AuthServiceProvider.php:
public function boot()
{
Auth::viaRequest('custom-auth', function ($request) {
// Any custom user-lookup logic here. For example:
if ($request->header('x-session')) {
$user = // ...try to fetch a user...
return $user;
}
});
}
As we can see, we just pass a closure to the viaRequest() method that returns a User object when authentication succeeds, or null when authentication fails.
Next, we'll tell Laravel about our new auth guard by adding an entry to the 'guards' array in config/auth.php:
'guards' => [
...
'broadcasting' => [
'driver' => 'custom-auth',
],
],
Finally, we need to update the middleware for any routes that should authenticate a user with our custom Guard. We can use Laravel's built-in auth middleware and specify which guard to apply as a middleware parameter. For example, we'll initialize the broadcasting routes in the question's BroadcastServiceProvider.php:
Broadcast::routes([ 'middleware' => [ 'auth:broadcasting', ... ] ]);
...where broadcasting matches the name we assigned to our custom Guard in config/auth.php.
This approach enables us to use all of Laravel's Auth services, provides a more central place to define our authentication logic, and simplifes automated testing because we can more easily mock up authentication as needed.
I actually managed to find a solution, so all I needed to do was bind the $user that I got in my custom auth middleware to the request by doing the following thing:
$request->merge(['user' => $user]);
//add this
$request->setUserResolver(function () use ($user) {
return $user;
});
and now $request->user() which laravel checks returns the user object and passes the validation.
Related
I'm wondering what is a better practice using the API with a token or the web router with a prefix:
Have a token that changes every time a user logs in. API:
Route::get('userdata/{key}', 'userController#show');
vs
Route::prefix('api')->group(function () {
Route::get('userdata', 'userController#show');
});
In the web router I can use cookies to verify an user.
Which of these is better?
You don`t need to use any 'key' or something fields in route/body to get data for logged / session user. You can create static route for get 'self-data' and fetch current user data according header (bearer or something other auth) or cookies.
Route::get('userdata', 'userController#show');
with cookies/header will be ok. Also don`t forget to use auth middleware, where you should validate cookies/header.
Group all the api routes and apply your custom middleware that will check for the bearer token in request headers. Here is how to do it:
Create a middleware:
class AuthenticateWithToken
{
public function handle($request, Closure $next)
{
$token = $request->bearerToken();
if ($token==='valid') {
return $next($request);
}
throw new Exception();
}
}
Register your middleware in App\Http\Kernel.php:
class Kernel extends HttpKernel
{
protected $routeMiddleware = [
'authenticateWithToken' => AuthenticateWithToken::class
];
}
Apply middleware on routes:
Route::middleware('authenticateWithToken')
->prefix('api')
->group(function () {
Route::get('/route1', 'YourController#action1');
Route::get('/route2', 'YourController#action2');
});
I have a requirement where there is an API method (guarded by the well-known package tymon/jwt-auth) and I need to also be able to access it using the basic session based web middleware.
I don't want to repeat the route in both api.php and web.php even though that would totally work.
I tried adding both to the route but they simply don't work, like: ['auth:api', 'web']
I also tried creating a new middleware with the intention of checking both api and web like so:
class CombinedAuth
{
public function handle($request, Closure $next)
{
$web = Auth::guard('web')->user();
if ($web) {
return $next($request);
}
$api = Auth::guard('api')->user();
if ($api) {
return $next($request);
}
if(!$web && !$api) {
throw new AuthorizationException;
}
}
}
and that also doesn't work. The api middleware works fine but the web middleware doesn't and it always signs me out and redirects to the login page.
So Is there a neat way of protecting a route with api and web middlewares at the same time in Laravel 5.8?
You can use 'auth:api,web' to check for multiple guards.
Using multiple can middleware calls in Laravel 9 route;
<?php
Route::get('/', function () {
return view('money');
})
->middleware([
'can:manage,App\Models\Payment',
'can:manage,App\Models\Withdraw',
]);
?>
I am today in bit confusion about my website security and some extra code that is written to make the website secure. Below are 2 locations where security is applied.
Inside Route Config, To secure the route, I have used Middleware to check the user role.
Route::group(['middleware' => ['web', 'SuperAdmin', 'auth']], function () {
Route::get('/Create-Department', 'DepartmentController#CreateDepartment');
});
I mentioned 2 Middlewares.
Auth Middleware : This is for authentication.
SuperAdmin Middleware: This is for Authorization.
Second location is Request class. Here is the code. In authorize method, again same thing is being checked as already done in route
class DepartmentRequest extends Request
{
public function authorize()
{
if(\Auth::user() == null) {
return false;
}
if(\Auth::user()->isSuperAdmin()) {
return true;
}
return false;
}
public function rules()
{
return [
'Department' => 'required',
];
}
}
Question: Should I remove check in Request class? Is that an unwanted validation to secure the request ? As route.config is already doing the job.
What's the use of authorize method? I meant, I am using Request class to validate form inputs. Should I always return true from authorize method?
yes, you should remove that checks in the Request class: if you're already doing that checks in your middleware you should not repeat them
When you specify this:
Route::group(['middleware' => ['web', 'SuperAdmin']], function () {
Route::get('/Create-Department', 'DepartmentController#CreateDepartment');
});
You're telling laravel that, when it finds a /Create-Department route, it should trigger the handle method of these middleware: ['web', 'SuperAdmin'], before the request is sent to the DepartmentController
So, if you check for authentication and authorization in the middlewares, when the request will get to your controller you're sure that it has satisfied all the middleware it went through
Regarding the purpose of the authorize method: the authorize method is usually used to authorize the actual request basing on some policy you'd like to respect. For example, if you have a request to edit a Post model, in the authorize method you'd check that the specific user trying to edit the post has the permissions to do it (for example being the author of the post )
EDIT
Even if you want to use a middleware for your authorization, it's fine. Anyhow, usually the authorize method within form requests is used to do authorization checks on the specific request.
For instance check this example from the docs :
public function authorize()
{
$postId = $this->route('post');
//here the authorization to edit the post is checked through the Gate facade
return Gate::allows('update', Post::findOrFail($postId));
}
In conclusion: if you're doing your authentication and authorization tasks in middlewares, you don't need to repeat them in the authorize method, but keep in mind that the native purpose of the method is to authorize the specific request
I have this in Laravel 4.2 Route::when('*', 'csrf', ['post']); that insert csrf verification to all post, how can I port to Larevel 5.2 ?
This is my own csrf, without using default provide by Laravel:
<?php
namespace App\Http\Middleware;
use Closure;
use Input;
class VerifyCsrfToken1
{
public function handle($request, Closure $next)
{
$token = $request->ajax() ? $request->header('X-CSRF-Token') : $request->input('_token');
if ($request->session()->token() === $token) {
return $next($request);
}
throw new TokenMismatchException;
}
}
I created my personal csrf middleware, but I don't know how to attach them on ALL post request
I want to attach it to all post via Route's facade. (file routes.php)
Thanks :)
Laravel 5 wires up middleware a bit differently, you won't be doing this through the Request facade.
You want to first register your Middleware as global. Open up app/Http/Kernel.php and add it to the global $middleware array.
protected $middleware = [
VerifyCsrfToken1::class
...
Then in your middleware class, check to see if it is handling a POST request. If not, have it just pass the request along without doing anything.
if($request->method() != "POST") {
// Move right along
return $next($request);
}
Side note: As you noted Laravel has a VerifyCsrfToken middleware baked in already. I'd advise trying to adapt this if possible.
I have an application which uses Laravel 5's out of the box authentication. I need to require authentication on the show method of a controller ONLY when the field called "approved" is equal to 1.
How can I require authentication using middlewares on a conditional basis such that unauthenticated users can access entries whose approved column is equal to 1, but only authenticated users can see entries where approved is equal to 0.
My constructor currently looks like this:
public function __construct(){
$this->middleware('auth', ['only' => ['edit', 'destroy', 'approve', 'markRecovered']]);
}
You may create your own middleware instead of using Laravel's default auth middleware and in that case, you may create a middleware such as 'checkApproval' using something like this:
php artisan make:middleware checkApproval
Then in your app/Http/Middleware directory you'll find the new middleware created and it'll contain the basic structure including handle method, so now you may erite code inside this middleware's handle method to check the user's state and the approved field and then either redirect to login page if condition doesn't match or allow access. here is a basic idea:
use Illuminate\Contracts\Auth\Guard;
class CheckPermission implements Middleware {
protected $auth;
public function __construct(Guard $auth)
{
$this->auth = $auth;
}
public function handle($request, Closure $next)
{
if($this->auth->guest() && !$this->checkApproval($request))
{
return redirect('login');
}
return $next($request);
}
protected function checkApproval($request)
{
// Get the auth/logged in user
// $user = $request->user();
// Get a parameter from route
// $id = $request->route()->parameter('id')
// Check the approved field here and return true or false
}
}
Now assign the middleware a short-hand key in your app/Http/Kernel.php file. By default, the $routeMiddleware property of this class contains entries for the middleware included with Laravel. To add your own, simply append it to this list and assign it a key to use in your route/controller, for example, checkApproval so in the place of auth you may use checkApproval for the the method view in your controller.
This is an abstract idea, but you can implement one of your own now so check the documentation for more information.