I have a laravel project which provides over rest api some public data on the other hand logged in users can manage some membership related datas.
Now in this dashboard I have couple of react components. Some of theme are simply fetching async datas and there are some which are interacting with the database in the meaning they patch, create, delete datas in function of user role. This endpoints should be protected.
I read about sanctum but I think for this scenario would be overhead. Is there any other approach to protect this api routes?
You can make it easy with laravel Auth. You need to add api_token field to your users table, if you don't have it. Generate access_token on register of the user. For protection of the route as middleware use auth:api.
In app\Http\Kernel.php you need to add api key to the $middlewareGroups
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'throttle:60,1',
'bindings',
],
];
After that add middleware guard to the route
Route::group(['middleware' => 'auth:api'], function () {}
When you are making request you need to add api_token param to your url ex: http://localhost/update?api_token=1234. This is the easiest way that I know. You can also send api_token as part of the body of the request to be secure.
You need to change also config/auth.php inside of guards key add this
'api' => [
'driver' => 'token',
'provider' => 'users',
],
Your config need looks similar to this
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
],
],
Related
I am using authentication plugin for login in cakephp.
application.php , configAuth() method code
protected function configAuth(): \Authentication\AuthenticationService
{
$authenticationService = new \Authentication\AuthenticationService([
// 'unauthenticatedRedirect' => '/cake_invo/users/login' , //<= manually working fine
'unauthenticatedRedirect' => \Cake\Routing\Router::url(['controller' => 'Users', 'action' => 'login']),
'queryParam' => 'redirect',
]);
// Load identifiers, ensure we check email and password fields
$authenticationService->loadIdentifier('Authentication.Password', [
'fields' => [
'username' => 'email',
'password' => 'password',
]
]);
// Load the authenticators, you want session first
$authenticationService->loadAuthenticator('Authentication.Session');
// Configure form data check to pick email and password
$authenticationService->loadAuthenticator('Authentication.Form', [
'fields' => [
'username' => 'email',
'password' => 'password',
],
// 'loginUrl' => '/cake_invo/users/login' //<= manually working fine
'loginUrl' => \Cake\Routing\Router::url(['controller' => 'Users', 'action' => 'login']),
]);
return $authenticationService;
}
middleware method
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
$middlewareQueue
->add(new ErrorHandlerMiddleware(Configure::read('Error')))
->add(new AssetMiddleware([
'cacheTime' => Configure::read('Asset.cacheTime'),
]))
->add(new RoutingMiddleware($this))
// add Authentication after RoutingMiddleware
->add(new \Authentication\Middleware\AuthenticationMiddleware($this->configAuth()));
return $middlewareQueue;
}
Getting error like image
How can I solve this problem ?
After give command bin/cake routes
The problem is that your routes aren't loaded yet when you make the call to Router::url(), hence it will fail.
While the order of the middlewares is correct, ie the authentication middleware is being added after the routing middleware, you are building the authentication service immediately by invoking $this->configAuth(), which means that the Router::url() call will be invoked before any of the middlewares have run, specifically before the routing middleware has run, which is responsible for loading your routes.
Instead of passing a built authentication service instance, setup things as shown in the docs, that is make sure that your Application class implements \Authentication\AuthenticationServiceProviderInterface, change your configAuth method to match AuthenticationServiceProviderInterface::getAuthenticationService(), and then pass $this to the authentication middleware constructor instead. That way the method will only be invoked when the authentication middleware runs, that is after the routing middleware.
// ...
use Authentication\AuthenticationServiceInterface;
use Authentication\AuthenticationServiceProviderInterface;
use Cake\Http\BaseApplication;
use Psr\Http\Message\ServerRequestInterface;
class Application extends BaseApplication implements AuthenticationServiceProviderInterface
{
// ...
public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
// ...
return $authenticationService;
}
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
$middlewareQueue
// ...
->add(new RoutingMiddleware($this))
->add(new AuthenticationMiddleware($this));
return $middlewareQueue;
}
// ...
}
i’m trying to make use of the cakeDC Users-Plugin 8.5.1 in a cakephp 3.8 but all i get is a message ‘you are not allowed to access that location’. I 'm using simple RBAC followed the instruction according the documentation on github. it seems the config/permissions.php is not read because when i write some bad syntax in this file nothing changes - no error messages. instead same behavior ‘you are not allowed …’
Here is what i did:
i used composer to install the plugin.
Since i use cakephp 3.8 the plugin is added in Application.php.
In the AppController.php initialize() i inserted
$config['Auth']['authorize']['Users.SimpleRbac'] = [
//autoload permissions.php
'autoload_config' => 'permissions',
//role field in the Users table
'role_field' => 'role',
//default role, used in new users registered and also as role matcher when no role is available
'default_role' => 'user',
/*
* This is a quick roles-permissions implementation
* Rules are evaluated top-down, first matching rule will apply
* Each line define
* [
* 'role' => 'admin',
* 'plugin', (optional, default = null)
* 'controller',
* 'action',
* 'allowed' (optional, default = true)
* ]
* You could use '*' to match anything
* Suggestion: put your rules into a specific config file
*/
'permissions' => [],
];
For the users table i have my own uprofiles table which contain the columns the plugin needs.
So in my UprofilesController.php i added the code to use the traits which come with the plugin.
In the config/users.php i changed the name of the table to ‘uprofiles’ and the name of the controller to ‘Uprofiles’.
The Auth part in the users.php is
'Auth' => [
'loginAction' => [
'plugin' => 'CakeDC/Users',
'controller' => 'Users',
'action' => 'login',
'prefix' => false
],
'authenticate' => [
'all' => [
'finder' => 'auth',
],
'CakeDC/Auth.ApiKey',
'CakeDC/Auth.RememberMe',
'Form',
],
'authorize' => [
'CakeDC/Auth.Superuser',
'CakeDC/Auth.SimpleRbac',
],
],
What is wrong ? Can someone help me?
I also tried to setup a superuser with the bake tool. That didn’t work.
Error: [BadMethodCallException] Unknown method "generateUniqueUsername" in .../vendor/cakephp/cakephp/src/ORM/Table.php on line 2524
Thank you for help
I am using Laravel 5.8.10, React 16.8, Laravel Echo Server 1.5.2, Redis 3.2.9, and Socket.io 2.2.0.
I am NOT using Pusher and don't want to use Pusher.
I am trying to create a basic chat system for site users. They log in normally using session authentication with email and password - all of that works fine.
There are 2 types of users: Brands and Influencers. Each has its own custom guard (web-brands & web-influencers). All session guards work normally.
I'm building the chat page using React. I can successfully join a public channel and receive messages on that public channel. However, the problem is when I try to make the channel private.
When I try to join a private channel, Laravel Echo Server sends an authentication request to: http://localhost:8000/broadcasting/auth.
But that returns the following 401 error:
{"message":"Unauthenticated."}
Client can not be authenticated, got HTTP status 401
Right now, I am trying to authenticate requests to /broadcasting/auth using a simple 'api_token' that is stored in the users tables (brands and influencers are the 2 users tables). This is a unique 60-character string.
I am trying this 'api_token' strategy because it sounds easier than setting up Laravel Passport, but perhaps I am wrong about that.
This is the constructor method from my React page:
import React, { Component } from 'react';
import Echo from "laravel-echo";
import Socketio from "socket.io-client";
constructor(props) {
super(props);
this.state = {
currentConversationId: conversations[0].id,
data: '',
};
this.selectConversation = this.selectConversation.bind(this);
let echo = new Echo({
broadcaster: 'socket.io',
host: 'http://localhost:6001',
client: Socketio,
auth: {
headers: {
// I currently have CSRF requirements disabled for /broadcasting/auth,
// but this should work fine once it is enabled anyway
'X-CSRF-Token': document.head.querySelector('meta[name="csrf-token"]'),
// I have the api_token hard-coded as I am trying to get it to work,
// but I have also used the javascript variable 'token' below
'api_token':'uUOyxRgCkVLKvp7ICZ0gXaELBPPbWEL0tUqz2Dv4TsFFc7JO4gv5kUi3WL3Q',
'Authorization':'Bearer: ' +'uUOyxRgCkVLKvp7ICZ0gXaELBPPbWEL0tUqz2Dv4TsFFc7JO4gv5kUi3WL3Q',
//'api_token':token,
//'Authorization':'Bearer: ' + token,
}
}
});
// Note that the ID of 1 is hardcoded for now until I get it to work
echo.private('brand.1')
.listen('SimpleMessageEvent', event => {
console.log('got something...');
console.log(event);
this.state.data = event;
});
}
Here you can see the in $php artisan route:list, the route is using auth:api middleware:
| GET|POST|HEAD | broadcasting/auth | Illuminate\Broadcasting\BroadcastController#authenticate | auth:api
Here is my BroadcastServiceProvider.php:
<?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()
{
Broadcast::routes(['middleware' => ['auth:api']]);
require base_path('routes/channels.php');
}
}
Here is my auth.php:
<?php
return [
'defaults' => [
'guard' => 'web-brands',
'passwords' => 'brands',
],
'guards' => [
'web-brands' => [
'driver' => 'session',
'provider' => 'brands',
],
'web-influencers' => [
'driver' => 'session',
'provider' => 'influencers',
],
'api' => [
'driver' => 'token',
'provider' => 'brands2',
],
],
'providers' => [
'brands' => [
'driver' => 'eloquent',
'model' => App\Brand::class,
],
'influencers' => [
'driver' => 'eloquent',
'model' => App\Influencer::class,
],
'brands2' => [
'driver' => 'database',
'table' => 'brands',
],
],
'passwords' => [
'brands' => [
'provider' => 'brands',
'table' => 'password_resets',
'expire' => 60,
],
'influencers' => [
'provider' => 'influencers',
'table' => 'password_resets',
'expire' => 60,
],
],
];
Here is my channels.php:
Broadcast::channel('brand.{id}',true);
Note that I have the brand.{id} set it to return true by default. I have also tried this for channels.php:
Broadcast::channel('brand.{id}', function ($brand,$id) {
return $brand->id === Brand::find($id)->id;
});
I have already tried testing the simple api_token method by using a dummy route:
Route::get('test-test-test',function(){return 'asdf';})->middleware('auth:api');
This test works:
http://localhost:8000/test-test-test results in redirect
http://localhost:8000/test-test-test?api_token=123 results in redirect
http://localhost:8000/test-test-test?api_token=[the actual correct 60-character token] results in 'asdf'
Here is some info from my .env:
BROADCAST_DRIVER=redis
QUEUE_DRIVER=redis
CACHE_DRIVER=file
QUEUE_CONNECTION=database
SESSION_DRIVER=file
SESSION_LIFETIME=120
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
Here is my laravel-echo-server.json:
{
"authHost": "http://localhost:8000",
"authEndpoint": "/broadcasting/auth",
"clients": [],
"database": "redis",
"databaseConfig": {
"redis": {},
"sqlite": {
"databasePath": "/database/laravel-echo-server.sqlite"
}
},
"devMode": true,
"host": null,
"port": "6001",
"protocol": "http",
"socketio": {},
"sslCertPath": "",
"sslKeyPath": "",
"sslCertChainPath": "",
"sslPassphrase": "",
"subscribers": {
"http": true,
"redis": true
},
"apiOriginAllow": {
"allowCors": false,
"allowOrigin": "",
"allowMethods": "",
"allowHeaders": ""
}
}
Perhaps I am not sending the api_token correctly in the header of the laravel echo request?
UPDATE/EDIT:
Now I have tried removing the auth:api middleware for the /broadcasting/auth route. I'm not sure if that was the correct thing to do.
That now produces a 403 error:
Client can not be authenticated, got HTTP status 403
UPDATE 2 - IMPORTANT
So I know this is not recommended, but I started changing some things inside of the laravel source files... I got it to work finally and now that I have figured it out, I would like to override the source files that I changed instead of actually changing them. I did save the originals so I can easily revert back.
One big challenge was that while changing the source files, I was not able to use the where() method, only the find() method to lookup users.
The key function that needed changing was retrieveUser() (which is located inside of Illuminate/Broadcasting/Broadcasters/Broadcaster.php.
The problem was that it kept trying to run:
return $request->user();
...but that user() function never worked, which is why it always returned a 403 forbidden error. I think it is because the actual Laravel Echo request was sent from React (in javascript frontend), so there was no user object attached to the request. In other words, it was like a guest making the request. That explains why the public channels worked, but the private ones didn't.
I never did figure out how to get the user information to be sent with the request through React, but I did figure out a workaround.
Basically what I had to do:
In my controller, encrypt the ID of the user and pass it to javascript as a variable.
Pass the encrypted ID variable through the Echo request as part of the header.
Modify the retrieveUser() function to use find(Crypt::decrypt($id)) to lookup the user instead of ->user() (or where() which was strangely not allowed).
From what I can tell, this seems like a decent strategy from a security perspective, but perhaps readers could point out if that is actually correct.
To hack your way into a private channel, you would have to know the ID of the channel you want to listen to, then pass it as an encrypted variable in the header of the request.
Maybe a potential hacker could say that he/she wants to listen to private channel 'brand.1' and all they would have to do is encrypt the number 1 and pass it through the header. I guess I don't know how that works enough to know whether that is possible.
Anyway my goals now are:
converting this into an override setup instead of explicitly changing the Laravel source code.
figuring out if passing the encrypted ID through the request header is secure enough for production.
It does seem like the encrypted ID in the header (which does change every time you run the request) is more secure than simply passing through an 'api_token' which would be a value stored in the users table and is what most people seem to do.
I am building a single page web application using Yii2(basic) and Angular 5. My backend is a module rather than a separate Yii application.
$config = [
... codes .....
'modules' => [
'backend' => [
'class' => 'app\modules\backend\Module',
'defaultRoute' => 'admin',
],
]
];
The page navigation is managed from angular side as #route ( hashtag route url) like [root_path]/web/#/user/dashboard (for frontend) and [root_path]/web/backend/#/admin/dashboard (for backend).
So, whenever i navigate to [root_path]/web/backend, i want to automatically redirect the url to [root_path]/web/backend/#/admin/dashboard. For this, i tried changing the default route of backend module as:
$config = [
... codes .....
'modules' => [
'backend' => [
'class' => 'app\modules\backend\Module',
'defaultRoute' => \yii\helpers\Url::to(['/admin','#' => '/admin/dashboard']),
],
]
];
AND
$config = [
... codes .....
'modules' => [
'backend' => [
'class' => 'app\modules\backend\Module',
'defaultRoute' => \yii\helpers\Url::to(['/backend/admin/index','#' => '/admin/dashboard']),
],
]
];
But I got this error :
Can anyone point me out what I am doing wrong?
Thanx in advance.
You cannot use Url::to() on defining configuration. At this point application is not yet initialized, so UrlManager component (which is used by Url helper) does not exist. So you're creating chicken-egg problem - you need Application to initialize Application.
And even if you could use it, there is no much sense in what you're trying to do. defaultRoute is not the same as URL and it has nothing to do with redirections - assigning URL into it will not bring anything good.
Moreover, part of URL after # is not sent to the server, so you never get request for URL /web/backend/#/admin/dashboard. From Yii perspective there is no difference whether user is on /web/backend/#/admin/dashboard or /web/backend/ - it will always be seen as /web/backend/.
If you want such redirection, you should handle it in JavaScript and perform at browser level.
I am using basic authentication in my project to access Api. In ApiController, I added below code in beforeFilter:
$this->Auth->config('authenticate', [
'Basic' => [
'fields' => ['username' => 'username', 'password' => 'api_key'],
'userModel' => 'Users'
]
]);
So from chrome postman application, I am sending post request with basic auth credentials. for example like below:
So when I send a request, I get unauthorized error back.
you are sending a post request with a 'password' field
Your application is expecting a 'api_key' field that would contain the password.
I think you missed this one script in your model entity.
use Cake\Auth\DefaultPasswordHasher;
protected function _setPassword($password)
{
if (strlen($password) > 0) {
return (new DefaultPasswordHasher)->hash($password);
}
}
Put this one in Model/Entity/User.php