›
›
›
  1. docs
  2. ›
  3. byrcsc/laravel-dev-login
1.x
Browse documentationOpenClose

Getting started

  • Introduction
  • Installation and setup
  • Quick start

Core concepts

  • Profiles
  • Safety gates
  • The login flow

Extending

  • User resolvers
  • Tenancy
  • Routes and redirects
  • Customizing the page

Reference

  • Configuration
  • Testing
  • Troubleshooting

Getting started

  • Introduction
  • Installation and setup
  • Quick start

Core concepts

  • Profiles
  • Safety gates
  • The login flow

Extending

  • User resolvers
  • Tenancy
  • Routes and redirects
  • Customizing the page

Reference

  • Configuration
  • Testing
  • Troubleshooting

byrcsc/laravel-dev-login · 1.x

Tenancy.

Let a profile carry a tenant by implementing the TenantResolver contract for your tenancy package.

A profile can name a tenant, and clicking it makes that tenant current before authenticating. One click then puts you in the right account and the right tenant.

The package ships the contract and no adapter. How a tenant becomes the current one is something only your tenancy package knows, so this page is about writing the twenty lines that connect the two.

If your application has no tenancy, nothing here applies. Profiles without a tenant never reach a resolver.

1. Implement the contract

namespace ByRcsc\LaravelDevLogin\Contracts;

use ByRcsc\LaravelDevLogin\Profile;

interface TenantResolver
{
    public function makeCurrent(Profile $profile): void;
}

The value to act on is $profile->tenant, exactly as it was written in config. A string stays a string and an integer stays an integer, because the package hands it over untouched.

Returning means the tenant is current. A tenant that cannot be made current throws.

A typical implementation looks up the tenant and hands it to whatever your tenancy package uses to switch:

namespace App\DevLogin;

use App\Models\Tenant;
use ByRcsc\LaravelDevLogin\Contracts\TenantResolver;
use ByRcsc\LaravelDevLogin\Exceptions\TenantNotResolved;
use ByRcsc\LaravelDevLogin\Profile;

final class MakeTenantCurrent implements TenantResolver
{
    public function makeCurrent(Profile $profile): void
    {
        $tenant = Tenant::query()->find($profile->tenant);

        if ($tenant === null) {
            throw TenantNotResolved::make($profile);
        }

        // Whatever your tenancy package calls to switch: swapping a
        // connection, setting a cache prefix, binding the current tenant.
        $tenant->makeCurrent();
    }
}

Throwing TenantNotResolved::make($profile) is optional. Any exception is wrapped in one, so a resolver that lets its own failure escape produces the same message.

Resolvers are built through the container, so constructor injection works. The package's demo application writes the tenant into the session in nine lines, which is enough to prove the switch happened.

2. Register it

// config/dev-login.php
'tenant_resolver' => App\DevLogin\MakeTenantCurrent::class,

This is a class-string rather than a closure, so the config file survives config:cache. A class-string that does not implement TenantResolver throws InvalidConfiguration naming the key to edit.

3. Give profiles a tenant

'profiles' => [
    'acme-owner' => [
        'label' => 'Owner',
        'email' => 'owner@acme.test',
        'tenant' => 'acme',
    ],
    'globex-owner' => [
        'label' => 'Owner',
        'email' => 'owner@globex.test',
        'tenant' => 'globex',
    ],
    'staff' => [
        'label' => 'Staff',
        'email' => 'staff@example.com',
    ],
],

The page groups buttons under a heading per tenant, in the order the tenants were configured. Profiles naming no tenant come last, under no heading. Both Owner buttons above can carry the same label, because the heading says which is which.

Ordering, and why it is fixed

The tenant is made current before the user is resolved, so the lookup happens inside the tenant. A tenant-scoped user provider therefore finds the right row without the resolver doing anything special.

The order also decides what a failure means. A tenancy failure happens before anybody is authenticated, so it can never leave you logged into the wrong tenant.

Failures

Three things can go wrong, and all three stop the login:

SituationException
A profile names a tenant, tenant_resolver is nullTenantNotResolved::noResolver
The configured class does not implement the contractInvalidConfiguration
The resolver throwsTenantNotResolved::make

Whatever your tenancy package threw is kept as the previous exception, so the stack trace still shows the real cause underneath a message naming the profile and the tenant:

Dev login profile [acme-owner] could not make the tenant [acme] current.

A profile that names a tenant with no resolver configured is a button that cannot do what it says, which is why that case throws rather than logging you in without the tenant.

Redirects for tenant-bound profiles

A tenant-bound profile ignores the session's intended URL. A URL captured in one tenant rarely means anything in another, so the redirect falls through to default_redirect and then to / unless the profile names its own.

Give tenant profiles an explicit redirect when the tenant has a landing page of its own.

There is no way to end tenancy

The contract has one method. Nothing in the login flow would call a matching teardown, so none exists.

A failure after makeCurrent() returns, such as a profile pointing at a user who does not exist, leaves the tenant current for the rest of a request that is about to end on an error page.

What to read next

  • Profiles for the other keys a tenant-bound profile can set.
  • The login flow for where tenancy sits in the sequence.
  • User resolvers for the step that runs inside the tenant.
PreviousUser resolversNextRoutes and redirects
View source

On this page

  1. 1. Implement the contract
  2. 2. Register it
  3. 3. Give profiles a tenant
  4. Ordering, and why it is fixed
  5. Failures
  6. Redirects for tenant-bound profiles
  7. There is no way to end tenancy
  8. What to read next