In my Laravel-5.8 application, I have a multi-company application using a single database. Each table have a company_id derived from the company table as shown below:
id | company_name | subdomain
1 | Main |
2 | Company1 | company1
3 | Company2 | company2
Main=> localhost:8888/myapp
Company1=>localhost:8888/company1.myapp
Company2=>localhost:8888/company2.myapp
I created a middleware:
class VerifyDomain
{
public function handle($request, Closure $next)
{
$domain == "myapp"; // your company app name
$path = $request->getPathInfo(); // should return /company1.myapp or /company2.myapp or /myapp
if (strpos($path, ".") !== false) { // if path has dot.
list($subdomain, $main) = explode('.', $path);
if(strcmp($domain, $main) !== 0){
abort(404); // if domain is not myapp then throw 404 page error
}
} else{
if(strcmp($domain, $path) !== 0){
abort(404); // if domain is not myapp then throw 404 page error
}
$subdomain = ""; // considering for main domain value is empty string.
}
$company = Company::where('subdomain', $subdomain)->firstOrFail(); // if not found then will throw 404
$request->session()->put('subdomain', $company); //store it in session
return $next($request);
}
}
Already, I have two (2) route groups in the route/web.php wgich looks like this:
Route::get('/', ['as' => '/', 'uses' => 'IndexController#getLogin']);
Auth::routes();
Route::get('/dashboard', 'HomeController#index')->name('dashboard');
// Config Module
Route::group(['prefix' => 'config', 'as' => 'config.', 'namespace' => 'Config', 'middleware' => ['auth']], function () {
Route::resource('countries', 'ConfigCountriesController');
Route::resource('nationalities', 'ConfigNationalitiesController');
});
// HR Module
Route::group(['prefix' => 'hr', 'as' => 'hr.', 'namespace' => 'Hr', 'middleware' => ['auth']], function () {
Route::resource('designations', 'HrDesignationsController');
Route::resource('departments', 'HrDepartmentsController');
Route::resource('employee_categories', 'HrEmployeeCategoriesController');
});
I have 2 issues:
If subdomain field is null, then the route should be for main domain: Main=> localhost:8888/myapp else localhost:8888/company1.myapp or localhost:8888/company2.myapp
2.How do I accomodate the route groups above into this:
Route::domain('localhost:8888/myapp')->group(function () {
Route::get('/', function ($id) {
//
});
});
Route::domain('localhost:8888/{subdomain}.myapp')->group(function () {
Route::get('/', function ($company_name, $id) {
$company = Company::where('subdomain', $subdomain)->firstOrFail();
// send the value of $company to data to send different view data
});
});
I'm not really sure that i understand you clearly. But, I hope you'll understand me :)
First thing is you're "domain". I suppose it's not real domain, but just uri. And maybe you should use it something like that:
Auth::routes();
$defaultDomain = config('myconfig.default_domain_name', 'myapp');
// I'm not reccomend to you use localhost:8888 here.
Route::domain('localhost:8888')
->group([
'middleware' => ['veryfy_domain'] // Add your VerifyDomain Middleware here
], function () {
// Here you already have a 'subdomain' param in session
// If you need special logic for default domain, you can out it here
Route::group(['prefix' => '/' . $defaultDomain], function () {
Route::get('/', function ($id) {
//
});
});
// Code below will work only with companies.
Route::group(['prefix' => '/{test}.' . $defaultDomain], function () {
Route::get('/', ['as' => '/', 'uses' => 'IndexController#getLogin']);
Route::get('/dashboard', 'HomeController#index')->name('dashboard');
// Config Module
Route::group(['prefix' => 'config', 'as' => 'config.', 'namespace' => 'Config', 'middleware' => ['auth']], function () {
Route::resource('countries', 'ConfigCountriesController');
Route::resource('nationalities', 'ConfigNationalitiesController');
});
// HR Module
Route::group(['prefix' => 'hr', 'as' => 'hr.', 'namespace' => 'Hr', 'middleware' => ['auth']], function () {
Route::resource('designations', 'HrDesignationsController');
Route::resource('departments', 'HrDepartmentsController');
Route::resource('employee_categories', 'HrEmployeeCategoriesController');
});
});
});
And about your middleware. I see it smth like that:
class VerifyDomain
{
public function handle($request, Closure $next)
{
$request->get('domain_name', $this->getBaseDomain());
$company = Company::where('subdomain', $subdomain)->firstOrFail();
$request->session()->put('subdomain', $company);
return $next($request);
}
// Better to store it in config
protected function getBaseDomain()
{
return config('myconfig.default_domain_name', 'myapp');
}
}
If you really want to use different domains, I think you need in your nginx something like this:
server_name *.myapp myapp;
And of course in your hosts file.
Than you can check it like that:
http://company.myapp
http://company1.myapp
http://myapp
Config example:
Create new file your_project_dir/app/config/myconfig.php (name it as you want)
Put this code in the file:
return [
'default_domain_name' => 'myapp'
];
Now you can use in in youre code as i suggest:
config('myconfig.default_domain_name');
Related
I have developed an application using Laravvel-5.8.
I have developed the application for just a single company whereby each table have a company_id derived from the company table as shown below:
class Company extends Model
{
protected $table = 'companies';
protected $fillable = [
'id',
'company_name',
'subdomain',
];
}
class User extends Authenticatable
{
protected $fillable = [
'name',
'company_id',
'email',
];
}
and the route/web.php looks like this:
Route::get('/', ['as' => '/', 'uses' => 'IndexController#getLogin']);
Auth::routes();
Route::get('/dashboard', 'HomeController#index')->name('dashboard');
// Config Module
Route::group(['prefix' => 'config', 'as' => 'config.', 'namespace' => 'Config', 'middleware' => ['auth']], function () {
Route::resource('countries', 'ConfigCountriesController');
Route::resource('nationalities', 'ConfigNationalitiesController');
});
// HR Module
Route::group(['prefix' => 'hr', 'as' => 'hr.', 'namespace' => 'Hr', 'middleware' => ['auth']], function () {
Route::resource('designations', 'HrDesignationsController');
Route::resource('departments', 'HrDepartmentsController');
Route::resource('employee_categories', 'HrEmployeeCategoriesController');
});
The main route is :
Route::get('/', ['as' => '/', 'uses' => 'IndexController#getLogin']);
which gives localhost:8888/myapp
config/app.php:
'env' => env('APP_ENV', 'production'),
'url' => env('APP_URL', 'localhost:8888/myapp'),
'asset_url' => env('ASSET_URL', null),
I'm asked to add sub-domains where each will see the data based on the company_id in each table
localhost:8888/myapp
localhost:8888/company1.myapp
localhost:8888/company2.myapp
All will use:
Route::get('/', ['as' => '/', 'uses' => 'IndexController#getLogin']);
After successful login will redirect to:
Route::get('/dashboard', 'HomeController#index')->name('dashboard');
Also each will only see it's data based on company_id
companies table:
id | company_name | subdomain
1 | Main |
2 | Company1 | company1
3 | Company2 | company2
Main=> localhost:8888/myapp
Company1=>localhost:8888/company1.myapp
Company2=>localhost:8888/company2.myapp
How do I modify:
route/web.php
config/app.php
to achieve this?
Thanks
There are two methods by which It can be done.
By Middleware
As per your question, login page urls can be localhost:8888/myapp, localhost:8888/company1.myapp, or localhost:8888/company2.myapp
Using middleware, we will keep the company name in the session using middleware. You can use it from session.
php artisan make:middleware VerifyDomain
will create a domain
<?php
namespace App\Http\Middleware;
use Closure;
class VerifyDomain
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$domain == "myapp"; // your company app name
$path = $request->getPathInfo(); // should return /company1.myapp or /company2.myapp or /myapp
if (strpos($path, ".") !== false) { // if path has dot.
list($subdomain, $main) = explode('.', $path);
if(strcmp($domain, $main) !== 0){
abort(404); // if domain is not myapp then throw 404 page error
}
} else{
if(strcmp($domain, $path) !== 0){
abort(404); // if domain is not myapp then throw 404 page error
}
$subdomain = ""; // considering for main domain value is empty string.
}
$company = Company::where('company_name', $subdomain)->firstOrFail(); // if not found then will throw 404
$request->session()->put('company_name', $company); //store it in session
return $next($request);
}
}
Then add it to route middleware.
In your controller you can redirect to different dashboards as per the company names stored in session.
In route groups
Route::domain('localhost:8888/myapp')->group(function () {
Route::get('/', function ($id) {
//
});
});
Route::domain('localhost:8888/{company_name}.myapp')->group(function () {
Route::get('/', function ($company_name, $id) {
$company = Company::where('company_name', $company_name)->firstOrFail();
// send the value of $company to data to send different view data
});
});
I am using Laravel 5.6.* . Now I created saparate route for sub domian and main domain. also create middleware for subdomain. But when I browse my site its get error with "Missing required parameters for [Route: something] [URI: something]"
My Route file
$domain = '{s_domain}.' . parse_url(config('app.url'), PHP_URL_HOST);
Route::group(['domain' => $domain, 'middleware' => 'subdomain'], function () {
Auth::routes();
Route::group(['namespace' => 'Clients', 'middleware' => 'auth'], function () {
Route::get('dashboard', 'ClientDashboardController#index')->name('home');
});
});
$maindomain = parse_url(config('app.url'), PHP_URL_HOST);
Route::group(['domain' => $maindomain], function () {
// My Route
});
My Subdomain Middlware file
public function handle($request, Closure $next)
{
$subdomain = $request->route()->s_domain;
$customer = Customer::where( 'sub_domain', $subdomain )->where('status', 'On')->first();
if( ! $customer ) {
return redirect()->route('customer.inactive');
} else {
Config::set('database.connections.subdomain.database', $customer->customer_database);
Config::set('database.default', 'subdomain');
\DB::purge('subdomain');
\DB::reconnect('subdomain');
}
return $next($request);
}
I have in my web.php the following routes:
Route::get('admin', ['as' => 'admin.login', 'uses' => 'AdminController#login']);
Route::post('admin', ['as' => 'admin.postLogin', 'uses' => 'AdminController#postLogin']);
Route::group(['middleware' => 'isAdmin'], function () {
// problem is when I redirect to the following route
Route::get('admin/transactions', ['as' => 'admin.transactions', 'uses' => 'AdminController#transactions']);
});
and my controller as follow:
public function postLogin(Request $request){
//some logic
return Redirect::to('admin/transactions');
}
and the transaction action:
public function transactions(){
dd('ok');
}
but I still get Resource not found!! why?
Can anybody help me?
Note!!: I have other routes working fine so I don't think it's an .htaccess problem
EDIT!!
here is my middleware:
public function handle($request, Closure $next)
{
if (!Auth::check() || !Auth::user()->admin) {
return Redirect::to('/');
}
return $next($request);
}
Maybe use Route::middleware?
Route::middleware(['isAdmin'])->group(function () {
Route::get('admin/transactions', 'AdminController#transactions');
});
in my web application i have admin panel and i'm trying to make access to users that they have admin role by this code:
namespace App\Http\Middleware;
use Closure;
class CheckUserAdminRole
{
public function handle($request, Closure $next)
{
if (auth()->check()) {
if (auth()->check() && !auth()->user()->hasRole('admin')) {
auth()->logout();
return redirect(route('system.messages','userIsNotAdmin'));
}
}
return $next($request);
}
}
and in my routs i have this route group:
Route::group(['namespace' => 'Dashboard', 'middleware' => ['auth:web'], 'prefix' => 'dashboard'], function () {
$this->group(['prefix' => 'administrator'], function () {
$this->get('panel', 'AdminController#index');
});
my kernel:
protected $middleware = [
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
...
\App\Http\Middleware\CheckUserAdminRole::class,
];
now when i add my middleware as CheckUserAdminRole to route group like with this code:
Route::group(['namespace' => 'Dashboard', 'middleware' => ['auth:web','CheckUserAdminRole'], 'prefix' => 'dashboard'], function () {
i get this error:
Class CheckUserAdminRole does not exist
this codes couldn't resolve my problem:
php artisan route:clear
php artisan cache:clear
php artisan config:clear
composer dump-autoload
Instead of registering your middleware in the $middleware array, you should register it in $routeMiddleware like so:
protected $routeMiddleware = [
...
'checkAdmin' => \App\Http\Middleware\CheckUserAdminRole::class,
];
Note: registering a middlware in the $middleware array results in it being executed for every request and therefore is not applicable on specific routes.
Then you can use it in your routes with the checkAdmin name:
Route::group(['namespace' => 'Dashboard', 'middleware' => ['auth:web','checkAdmin'], 'prefix' => 'dashboard'], function () {
Source
You can also use middleware and route group together easily like so:
Route::group(['prefix' => 'admin', 'middleware' => 'auth'], function()
{
//All the routes that belongs to the group goes here
Route::get('dashboard', function() {} );
});
Here's a simple fix without touching the Kernel file and taking advantage of the Policy. The accessAdmin is a function created inside Policy file.
Route::group(['namespace' => 'Dashboard', 'middleware' => 'can:accessAdmin, App\User', 'prefix' => 'dashboard'], function () {
$this->group(['prefix' => 'administrator'], function () {
$this->get('panel', 'AdminController#index');
});
You can try middleware with prefix and groups.
Route::middleware(['Auth'])->prefix('api/')->group(function() {
Route::group(['prefix' => 'review/'], function () {
Route::get('/', 'User\Controllers\Api\UserController#getUserReviews');
});
});
Hope its helps
Can someone explain me the following behavior, when i enable my routes (login, homepage etc) like so:
Route::group(['middleware' => 'web'], function () {
....
});
A Ajax login modal is working correctly, however when i try the following (enabling middleware in the controllers) which i prefer working with:
class PagesController extends Controller
{
public function __construct()
{
$this->middleware('web');
}
...
}
class AuthController extends Controller
{
public function __construct()
{
$this->middleware('web');
$this->middleware('guest', ['except' => 'logout']);
}
...
}
A TokenMismatchException is trown in VerifyCsrfToken.php line 67.
To my knowledge there shouldn't be a difference in those two approaches, what am i doing wrong here?
csrf token setup:
Base layout:
<meta name="csrf-token" content="{{ csrf_token() }}">
modal js:
var options = {
emulateJSON: true,
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
};
I'll give You working example, take from there ideas that will help You:
app/Http/routes.php:
// all routes that start with: "/auth" are not filtered by any middleware
Route::group(['prefix' => 'auth'], function() {
Route::get('/', ['as' => 'auth', 'uses' => 'AuthController#index']);
Route::post('/', ['as' => 'auth.attempt', 'uses' => 'AuthController#attempt']);
Route::delete('/', ['uses' => 'AuthController#destroy']);
Route::any('destroy', ['as' => 'auth.destroy', 'uses' => 'AuthController#destroy']);
});
// all routes that start with: "/billing" will be handled by this group (prefix => 'billing')
// all controllers inside this route group are located in 'Billing' namespace
// all routes in this group are pre-checked by middleware 'HasAccessToBilling'
Route::group(['prefix' => 'billing', 'namespace' => 'Billing', 'middleware' => ['App\Http\Middleware\HasAccessToBilling']], function()
{
Route::any('/', ['as' => 'billing', 'uses' => 'DashboardController#index']);
Route::get('profile', ['as' => 'billing.profile', 'uses' => 'ProfileController#index']);
// TARIFFS
Route::group(['prefix' => 'tariffs'], function() {
Route::get('/', ['as' => 'billing.tariffs', 'uses' => 'TariffsController#index']); // showing page with tariffs paginated
Route::get('all', ['as' => 'billing.tariffs.all', 'uses' => 'TariffsController#all']); // listing all tariffs with json (see controller)
Route::get('create', ['as' => 'billing.tariffs.create', 'uses' => 'TariffsController#create']); // create form
Route::post('/', ['as' => 'billing.tariffs.store', 'uses' => 'TariffsController#store']); // creating
Route::get('{id}', ['as' => 'billing.tariffs.edit', 'uses' => 'TariffsController#edit']); // edit form
Route::post('{id}', ['as' => 'billing.tariffs.update', 'uses' => 'TariffsController#update']); // updating
Route::get('{id}/activate', ['as' => 'billing.tariffs.activate', 'uses' => 'TariffsController#activate']); // active = 1
Route::get('{id}/suspend', ['as' => 'billing.tariffs.suspend', 'uses' => 'TariffsController#suspend']); // active = 0
Route::get('{id}/delete', ['as' => 'billing.tariffs.delete', 'uses' => 'TariffsController#delete']); // deleted = 1
});
app/Http/Middleware/HasAccessToBilling.php:
<?php namespace App\Http\Middleware;
use App\Library\Auth;
use Closure;
use Illuminate\Http\Request;
class HasAccessToBilling
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle(Request $request, Closure $next)
{
if (Auth::hasAccessTo('billing', $request)) {
return $next($request);
}
return redirect()->route('auth');
}
}
app/Library/Auth.php:
<?php namespace App\Library;
use \App\Models\User;
use Illuminate\Http\Request;
use Crypt;
class Auth
{
public static function recoverSession(Request $request)
{
$rememberToken = $request->cookie('remember-token', null);
if(is_null($rememberToken)) {
return null;
}
try{
$rememberToken = Crypt::decrypt($rememberToken);
$auth = json_decode($rememberToken, true);
$request->session()->set('auth', $auth);
}
catch(\Exception $ex) {}
return $request->session()->get('auth');
}
public static function hasAccessTo($realm, Request $request)
{
$auth = $request->session()->get('auth', null);
if (is_null($auth)) {
$auth = self::recoverSession($request);
}
return (isset($auth['access_to']))?
in_array($realm, $auth['access_to'])
: false;
}
}
and finally example controller:
see namespace 'Billing' must be same with folder, otherwise You'll do manual class aliasing in composer.
app/Http/Controllers/Billing/TariffsController.php:
<?php namespace App\Http\Controllers\Billing;
use Illuminate\Http\Request;
use Redirect;
use App\Http\Controllers\Controller;
use App\Models\Tariff as Model;
class TariffsController extends Controller
{
/**
* Listing records
* #return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function index()
{
$records = Model::paginate();
return view('billing.tariffs.index', compact('records'));
}
/**
* Listing all tariff plans as json
* #return \Illuminate\Http\JsonResponse
*/
public function all()
{
return $this->ok(Model::all());
}
summary:
if You defined middleware in Route::group - so no need for calling middleware inside constructor. Idea of route group is to free You from repeating code when writing routes, when giving accesses using middlewares and etc.
auth controllers must not be guarded by middleware that denies public access if it's not application for intranet. so You can see that in my routes file I've not defined middleware for "/auth" prefix routes.
I don't use csrf tokens (I don't see any reason for it, many years of work I've not ever got the moment where csrf was helpful or saved my life), so I've removed it from Kernel.php.