A comprehensive multi-tenant authentication and authorization solution designed for Filament, with a focus on company-based tenancy.
After installing the Panel Builder, make sure that you have created a panel using the following command:
php artisan filament:install --panels
📝 If you've followed the Panel Builder documentation, you should have already done this.
Install the package
composer require andrewdwallo/filament-companies
Execute the following Artisan command to scaffold the application. You will be prompted to choose between installing the Base package or enabling Socialite support.
php artisan filament-companies:install
Run migrations:
php artisan migrate:fresh
After installation, there will be a company panel registered for your application. It is located within the FilamentCompaniesServiceProvider.php
file.
In order for Tailwind to process the CSS used within this package and for the company panel, a user must create a custom theme.
To create a custom theme for the company panel, you can use the following command:
php artisan make:filament-theme company
🛠️ Please follow the instructions in the console to complete the setup process
Here is a reference to the instructions that should show after running the command:
⇂ First, add a new item to the `input` array of `vite.config.js`: `resources/css/filament/company/theme.css`⇂ Next, register the theme in the company panel provider using `->viteTheme('resources/css/filament/company/theme.css')`⇂ Finally, run `npm run build` to compile the theme
After completing the process for creating a custom theme for the company panel, add this package's vendor directory into the content array of the tailwind.config.js
file that should be located in the resources/css/filament/company/
directory of your application:
export default { content: [ './resources/**/*.blade.php', './vendor/filament/**/*.blade.php', './vendor/andrewdwallo/filament-companies/resources/views/**/*.blade.php', // The package's vendor directory ], // ...}
As you may have noticed, after installation, there will be a company panel registered for your application. In order for this package to work you must also have a "User" panel to contain the Profile page and Personal Access Tokens page.
For this example, I will use the default panel that Filament provides when installing the panel builder, the "Admin" panel.
In your "Admin" panel, make sure to register the following pages:
use Wallo\FilamentCompanies\Pages\User\PersonalAccessTokens;use Wallo\FilamentCompanies\Pages\User\Profile; public function panel(Panel $panel): Panel{ return $panel // ... ->pages([ Profile::class, PersonalAccessTokens::class, ])}
🛑 You may create a separate User Panel following the documentation for creating a new panel
You must provide a way for your users to navigate to the Profile and Personal Access Tokens pages.
It would also be wise to allow your users to navigate back to the Company Panel.
You may use the following as a guide:
use Filament\Navigation\MenuItem;use Filament\Navigation\NavigationItem;use Illuminate\Support\Facades\Auth;use Wallo\FilamentCompanies\Pages\User\PersonalAccessTokens;use Wallo\FilamentCompanies\Pages\User\Profile; public function panel(Panel $panel): Panel{ return $panel // ... ->userMenuItems([ 'profile' => MenuItem::make() ->label('Profile') ->icon('heroicon-o-user-circle') ->url(static fn () => url(Profile::getUrl())), MenuItem::make() ->label('Company') ->icon('heroicon-o-building-office') ->url(static fn () => url(Pages\Dashboard::getUrl(panel: 'company', tenant: Auth::user()->personalCompany()))), ]) ->navigationItems([ NavigationItem::make('Personal Access Tokens') ->label(static fn (): string => __('filament-companies::default.navigation.links.tokens')) ->icon('heroicon-o-key') ->url(static fn () => url(PersonalAccessTokens::getUrl())), ])}
You may change the value used for the User Panel using the id
of the panel:
use Filament\Panel;use Wallo\FilamentCompanies\FilamentCompanies; class FilamentCompaniesServiceProvider extends PanelProvider{ public function panel(Panel $panel): Panel { return $panel // ... ->plugin( FilamentCompanies::make() ->userPanel('user') ) }}
🚧 Make sure to create a panel with the id you're passing
If you wish to translate the package, you may publish the language files using:
php artisan vendor:publish --tag=filament-companies-translations
If you wish to customize the views, you may publish them using:
php artisan vendor:publish --tag=filament-companies-views
If you would like, you may create a new account using:
php artisan make:filament-companies-user
📘 You may also create a new account by registering through the application.
Filament has a built-in event that is fired when the application needs to set the tenant for the current request. This event is Filament\Events\TenantSet
. If you would like to either enable or disable the ability to switch the current company, you may do so by using the switchCurrentCompany()
method in your FilamentCompaniesServiceProvider
class.
use Filament\Panel;use Wallo\FilamentCompanies\FilamentCompanies; class FilamentCompaniesServiceProvider extends PanelProvider{ public function panel(Panel $panel): Panel { return $panel // ... ->plugin( FilamentCompanies::make() ->switchCurrentCompany() ); }}
You can selectively enable or disable certain profile features. If you choose to omit a feature, it will be considered as disabled (false
) by default.
To do so, modify your FilamentCompaniesServiceProvider
class as shown below:
use Filament\Panel;use Wallo\FilamentCompanies\FilamentCompanies; class FilamentCompaniesServiceProvider extends PanelProvider{ public function panel(Panel $panel): Panel { return $panel // ... ->plugin( FilamentCompanies::make() ->updateProfileInformation() // Enables updating profile information ->updatePasswords() // Enables password updates ->setPasswords() // Enables setting passwords only if Socialite is enabled ->connectedAccounts() // Enables connected account management only if Socialite is enabled ->manageBrowserSessions() // Enables browser session management ->accountDeletion() // Enables account deletion ); }}
Personalize your application by replacing default components with your own custom components. This is done by passing your custom component's class name to the component parameter of the relevant method.
Important: Your custom component must have a unique class name. This is crucial to prevent conflicts and ensure proper functioning, as Livewire differentiates components primarily by their class names. Even if your custom component is in a different namespace, having the same class name as a component in the package can lead to unexpected errors and behavior.
Here's an example of how to use a custom component for updating profile information:
use App\Livewire\CustomComponent; FilamentCompanies::make() ->updateProfileInformation(component: CustomComponent::class);
If you would like to change the order of the profile features, you may do so by setting the sort
parameter to the corresponding method.
The default sort order is as follows:
FilamentCompanies::make() ->updateProfileInformation(sort: 0) ->updatePasswords(sort: 1) ->setPasswords(sort: 2) ->connectedAccounts(sort: 3) ->manageBrowserSessions(sort: 4) ->accountDeletion(sort: 5);
If you would like to add custom profile components, you may do so by passing the component class name along with the sort order to the addProfileComponents()
method:
use App\Livewire\CustomComponent; FilamentCompanies::make() ->addProfileComponents([ 7 => CustomComponent::class, ]);
Within your component's view, you may use the grid section component to match the style of other components:
<x-filament-companies::grid-section md="2"> <x-slot name="title"> {{ __('My Custom Component') }} </x-slot> <x-slot name="description"> {{ __('This is my custom component.') }} </x-slot> <x-filament::section> <x-filament-panels::form wire:submit="submit"> {{ $this->form }} <div class="text-left"> <x-filament::button type="submit"> {{ __('Save') }} </x-filament::button> </div> </x-filament-panels::form> </x-filament::section></x-filament-companies::grid-section>
To allow users to upload custom profile photos, you can enable this feature by including the profilePhotos()
method in your FilamentCompaniesServiceProvider
.
use Filament\Panel;use Wallo\FilamentCompanies\FilamentCompanies; class FilamentCompaniesServiceProvider extends PanelProvider{ public function panel(Panel $panel): Panel { return $panel // ... ->plugin( FilamentCompanies::make() ->profilePhotos() ) }}
By default, the package uses Laravel's public
disk for storing images. However, you can specify a different disk by passing the disk
parameter.
FilamentCompanies::make() ->profilePhotos(disk: 's3')
If you want to store profile photos in a specific directory, you can set the storagePath
parameter.
FilamentCompanies::make() ->profilePhotos(storagePath: 'profile-avatars')
To adjust the layout and behavior of modals, use the modals()
method. Below are the package's default settings:
use Filament\Panel;use Wallo\FilamentCompanies\FilamentCompanies; class FilamentCompaniesServiceProvider extends PanelProvider{ public function panel(Panel $panel): Panel { return $panel // ... ->plugin( FilamentCompanies::make() ->modals( width: '2xl', alignment: 'center', formActionsAlignment: 'center', cancelButtonAction: false ) ); }}
To configure the notifications that are sent by the package, use the notifications()
method.
Unless specified otherwise, the package will send notifications. In order to disable notifications, you must pass false
to the notifications()
method.
use Filament\Panel;use Wallo\FilamentCompanies\FilamentCompanies; class FilamentCompaniesServiceProvider extends PanelProvider{ public function panel(Panel $panel): Panel { return $panel // ... ->plugin( FilamentCompanies::make() ->notifications(condition: false) ); }}
To override the default notifications sent by the package, you should provide the following methods for each corresponding action.
The parameters passed to each method are optional and may be omitted if not needed.
\App\Actions\FilamentCompanies\UpdateUserProfileInformation::class /** @method void profileInformationUpdated(\Illuminate\Foundation\Auth\User|null $user = null, array|null $input = null) */
\App\Actions\FilamentCompanies\UpdateUserPassword::class /** @method void passwordUpdated(\Illuminate\Foundation\Auth\User|null $user = null, array|null $input = null) */
\App\Actions\FilamentCompanies\SetUserPassword::class /** @method void passwordSet(\Illuminate\Foundation\Auth\User|null $user, array|null $input = null) */
\App\Actions\FilamentCompanies\UpdateCompanyName::class /** @method void companyNameUpdated(\Illuminate\Foundation\Auth\User|null $user = null, \Illuminate\Database\Eloquent\Model|null $company = null, array|null $input = null) */
\App\Actions\FilamentCompanies\InviteCompanyEmployee::class /** @method void employeeInvitationSent(\Illuminate\Foundation\Auth\User|null $user = null, \Illuminate\Database\Eloquent\Model|null $company = null, string|null $email = null, string|null $role = null) */
\App\Actions\FilamentCompanies\DeleteCompany::class /** @method void companyDeleted(\Illuminate\Database\Eloquent\Model|null $company = null) */
If you would like to override the notification that is sent when a user updates their password, you may do the following:
<?php namespace App\Actions\FilamentCompanies; use App\Models\User;use Filament\Notifications\Notification;use Wallo\FilamentCompanies\Contracts\UpdatesUserPasswords; class UpdateUserPassword implements UpdatesUserPasswords{ /** * Validate and update the user's password. * * @param array<string, string> $input */ public function update(User $user, array $input): void { // ... } public function passwordUpdated(): void { Notification::make() ->title('Password Updated') ->body('Your password has been updated.') ->success(); ->send(); } }
Sign in to your account
Go to App passwords
Click on "Select app", enter name of Application, and then click "Generate".
Copy your app password and store it somewhere safe.
Add the credentials in your application's .env
file:
MAIL_MAILER=smtpMAIL_HOST=smtp.gmail.comMAIL_PORT=587MAIL_USERNAME=yourgmailusername@gmail.comMAIL_PASSWORD=of9f9279g924792g49tMAIL_ENCRYPTION=tlsMAIL_FROM_ADDRESS="filament@company.com"MAIL_FROM_NAME="${APP_NAME}"
You may change the roles & permissions in app/Providers/FilamentCompaniesServiceProvider.php
/** * Configure the roles and permissions that are available within the application. */protected function configurePermissions(): void{ FilamentCompanies::defaultApiTokenPermissions(['read']); FilamentCompanies::role('admin', 'Administrator', [ 'create', 'read', 'update', 'delete', ])->description('Administrator users can perform any action.'); FilamentCompanies::role('editor', 'Editor', [ 'read', 'create', 'update', ])->description('Editor users have the ability to read, create, and update.');}
By Default, the GitHub Provider will be enabled.
You may use any Provider that Laravel Socialite supports.
You may add or remove any Provider in the company panel configuration:
use Filament\Panel;use Wallo\FilamentCompanies\FilamentCompanies;use Wallo\FilamentCompanies\Providers;use Wallo\FilamentCompanies\Socialite; class FilamentCompaniesServiceProvider extends PanelProvider{ public function panel(Panel $panel): Panel { return $panel // ... ->plugin( FilamentCompanies::make() ->socialite( providers: [ Providers::github(), Providers::gitlab(), Providers::google(), Providers::facebook(), Providers::linkedin(), Providers::linkedinOpenId(), Providers::bitbucket(), Providers::slack(), Providers::twitter(), Providers::twitterOAuth2(), ], features: [ Socialite::rememberSession(), Socialite::providerAvatars(), Socialite::generateMissingEmails(), Socialite::loginOnRegistration(), Socialite::createAccountOnFirstLogin(), ] ) ) }}
⚠️ If Twitter is desired, you may only use either Twitter OAuth1 or Twitter OAuth2.
Pass your Provider's credentials in the provider's array in config/services.php
:
/*|--------------------------------------------------------------------------| Third Party Services|--------------------------------------------------------------------------|| This file is for storing the credentials for third party services such| as Mailgun, Postmark, AWS and more. This file provides the de facto| location for this type of information, allowing packages to have| a conventional file to locate the various service credentials.|*/ 'github' => [ 'client_id' => env('GITHUB_CLIENT_ID'), 'client_secret' => env('GITHUB_CLIENT_SECRET'), 'redirect' => 'https://filament.test/company/oauth/github/callback',],
‼️ The Provider's Redirect URI must look similar to the above (e.g. 'APP_URL/company/oauth/provider/callback')
Register a new OAuth application
Application name
Filament
http://filament.test/company
http://filament.test/company/oauth/github/callback
☑ Enable Device Flow
Click on Register application
Copy the Client Secret & store somewhere safe
Add the Client ID and Client Secret in .env
GITHUB_CLIENT_ID=aluffgef97f9f79f434tGITHUB_CLIENT_SECRET=hefliueoioffbo8338yhf2p9f4g2gg33
Wallo\FilamentCompanies\HasCompanies
trait.App\Models\User
model during installation.// Access a user's currently selected company...$user->currentCompany : Wallo\FilamentCompanies\Company // Access all of the companies (including owned companies) that a user belongs to...$user->allCompanies() : Illuminate\Support\Collection // Access all of a user's owned companies...$user->ownedCompanies : Illuminate\Database\Eloquent\Collection // Access all of the companies that a user belongs to but does not own...$user->companies : Illuminate\Database\Eloquent\Collection // Access a user's "personal" company...$user->personalCompany() : Wallo\FilamentCompanies\Company // Determine if a user owns a given company...$user->ownsCompany($company) : bool // Determine if a user belongs to a given company...$user->belongsToCompany($company) : bool // Get the role that the user is assigned on the company...$user->companyRole($company) : \Wallo\FilamentCompanies\Role // Determine if the user has the given role on the given company...$user->hasCompanyRole($company, 'admin') : bool // Access an array of all permissions a user has for a given company...$user->companyPermissions($company) : array // Determine if a user has a given company permission...$user->hasCompanyPermission($company, 'server:create') : bool
📘 $user represents the current user of the application. Interchangeable with
Auth::user()
/filament-companies
directory, create a branch for your fix, e.g. fix/error-message
.Install the package in your application's composer.json
file, using the dev
prefix followed by your branch's name:
{ ... "require": { "andrewdwallo/filament-companies": "dev-fix/error-message", }, "repositories": [ { "type": "path", "url": "filament-companies/" } ], ...}
Now, run composer update
and continue by following the installation instructions above.