PHPackages                             chuoke/laravel-user-identities - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Authentication &amp; Authorization](/categories/authentication)
4. /
5. chuoke/laravel-user-identities

ActiveLibrary[Authentication &amp; Authorization](/categories/authentication)

chuoke/laravel-user-identities
==============================

Multi-auth identities for Laravel: decouple auth data from your users table.

0.2.0(2mo ago)013MITPHPPHP &gt;=8.1

Since May 9Pushed 1mo agoCompare

[ Source](https://github.com/chuoke/laravel-user-identities)[ Packagist](https://packagist.org/packages/chuoke/laravel-user-identities)[ RSS](/packages/chuoke-laravel-user-identities/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (5)Dependencies (1)Versions (4)Used By (0)

Laravel User Identities
=======================

[](#laravel-user-identities)

A flexible and extensible authentication system for Laravel that enables multiple identity types per user, supporting password-based, OAuth, and token authentication methods.

Core Philosophy
---------------

[](#core-philosophy)

**Keep your users table lean** - Move authentication data out of users table and into a dedicated identities table. This approach:

- **Separates Concerns** - User data stays clean, auth data stays organized
- **Improves Performance** - User queries remain fast, auth queries are optimized
- **Enables Flexibility** - Add new auth methods without touching users table
- **Better Analytics** - Clear separation of authentication methods per user
- **Enhanced Security** - Minimize sensitive data exposure in user queries, logs, and debugging

Features
--------

[](#features)

- 🎯 **Separation of Concerns**: Decouple authentication data from user business data
- 🔄 **Highly Extensible**: Easy to add new identity types without schema changes
- ✅ **Independent Verification**: Track verification status for each identity separately
- 🛡️ **Secure by Default**: Automatic password hashing and credential protection
- 🔌 **Laravel Integration**: Custom UserProvider for seamless authentication with default guards
- ⚡ **Performance Optimized**: Smart caching to minimize database queries
- 🔐 **Remember Me**: Token stored in identities table, no `remember_token` column needed on users
- 🔑 **Two-Factor Auth**: Fortify 2FA bridge — stores secret &amp; recovery codes as identity, zero user columns
- 🌐 **Socialite Ready**: One-line OAuth identity binding from Socialite callbacks
- 🔁 **Password Rehash**: Automatic bcrypt rehash support for Laravel 11+

Installation
------------

[](#installation)

```
composer require chuoke/laravel-user-identities
```

Publish configuration and migrations:

```
php artisan vendor:publish --tag=user-identities-config
php artisan vendor:publish --tag=user-identities-migrations
php artisan migrate
```

Quick Start
-----------

[](#quick-start)

### 1. Add Traits to User Model

[](#1-add-traits-to-user-model)

**Option A: Extend the package's base User class**

```
use Chuoke\UserIdentities\Auth\User as IdentityUser;

class User extends IdentityUser
{
    // All traits are pre-configured with proper method resolution
}
```

**Option B: Add traits to your existing User model** (recommended)

```
use Chuoke\UserIdentities\Concerns\HasIdentities;
use Chuoke\UserIdentities\Concerns\HasIdentityAuth;

class User extends Authenticatable
{
    use HasIdentities, HasIdentityAuth;

    // HasIdentityAuth overrides getAuthPassword(), getRememberToken(), etc.
    // to read/write from the identities table instead of user columns.
}
```

### 2. Configure Laravel Auth

[](#2-configure-laravel-auth)

```
// config/auth.php
'guards' => [
    'web' => [
        'driver' => 'session',            // Keep Laravel's default session guard
        'provider' => 'user-identity',    // Use the identity-aware provider
    ],
],

'providers' => [
    'user-identity' => [
        'driver' => 'user-identity',
        'model' => App\Models\User::class,
    ],
],
```

> **Note:** No custom guard driver is needed. Laravel's built-in `SessionGuard` works perfectly with the `user-identity` provider. All identity-specific logic (credential mapping, lookup, validation, remember-me, rehash) is handled entirely in the provider layer.

### 3. Create Identities

[](#3-create-identities)

```
use Chuoke\UserIdentities\Actions\UserIdentityCreate;

$action = new UserIdentityCreate();

// Password-based identities
$action->execute($user, 'email', 'user@example.com', 'password123');
$action->execute($user, 'mobile', '+1234567890', 'password123');
$action->execute($user, 'username', 'johndoe', 'password123');

// OAuth identities (pre-verified)
$action->execute($user, 'github', 'github_id_12345', 'oauth_token_here', true);

// Token-based identities
$action->execute($user, 'api_key', 'my-app', 'secret_api_key_here', true);
```

### 4. Authenticate Users

[](#4-authenticate-users)

```
// Explicit format
Auth::attempt([
    'type' => 'email',
    'identifier' => 'user@example.com',
    'password' => 'password123'
]);

// Fortify/Breeze compatible format (via credential_field_mapping)
Auth::attempt([
    'email' => 'user@example.com',
    'password' => 'password123'
]);

// With remember me (token stored in identities table, not users table)
Auth::attempt([
    'email' => 'user@example.com',
    'password' => 'password123'
], remember: true);
```

Fortify Integration
-------------------

[](#fortify-integration)

This package is designed to work with [Laravel Fortify](https://laravel.com/docs/fortify) with minimal configuration. The `IdentityUserProvider` speaks the same credential dialect as Fortify out of the box.

### Login (Zero Changes Required)

[](#login-zero-changes-required)

Fortify calls `Auth::attempt(['email' => ..., 'password' => ...])` internally. The `credential_field_mapping` config maps `email` → identity type `email`, so **no Fortify customization is needed** for standard login.

```
// config/user-identities.php — already configured by default
'credential_field_mapping' => [
    'email'    => 'email',
    'mobile'   => 'mobile',
    'username' => 'username',
],
```

To allow login by username instead of (or in addition to) email, just add `'username' => 'username'` to the mapping and Fortify's login form works automatically.

---

### Password Confirmation — `ConfirmPassword` Action

[](#password-confirmation--confirmpassword-action)

Fortify's default password confirmation checks the configured Fortify username field on the user model. If you keep auth fields out of the users table, register the package action explicitly:

```
use Chuoke\UserIdentities\Fortify\Actions\ConfirmPassword;
use Laravel\Fortify\Fortify;

Fortify::confirmPasswordsUsing(app(ConfirmPassword::class));
```

> **Note for OAuth-only users:** If a user has no password-based identity (e.g. signed up via GitHub only), the confirm-password check will always fail. This is correct security behaviour — these users have no password to confirm. You should redirect them or provide an alternative confirmation flow.

---

### Registration — `UserIdentityCreate` Action

[](#registration--useridentitycreate-action)

`UserIdentityCreate` is a general create action, not a Fortify contract implementation. For registration, keep Fortify's `CreatesNewUsers` implementation in your application and call the package action after creating the authenticatable:

```
use App\Models\User;
use Chuoke\UserIdentities\Actions\UserIdentityCreate;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rules\Password;
use Illuminate\Support\Facades\DB;
use Laravel\Fortify\Contracts\CreatesNewUsers;

class CreateNewUser implements CreatesNewUsers
{
    public function create(array $input): User
    {
        Validator::make($input, [
            'name'     => ['required', 'string', 'max:100'],
            'username'     => ['required', 'string', 'max:100', 'unique:users'],
            'email'    => ['required', 'string', 'email', 'max:255', ],
            'password' => ['required', 'confirmed', Password::defaults()],
        ])->validate();

        return DB::transaction(function () use ($input) {
            $user = User::create([
                'name' => $input['name'],
                'username' => $input['username'],
            ]);

            app(UserIdentityCreate::class)->execute(
                $user,
                type:        'email',
                identifier:  $input['email'],
                credentials: $input['password'],
                verified:    false, // requires email verification
            );

            app(UserIdentityCreate::class)->execute(
                $user,
                type:        'username',
                identifier:  $input['username'],
                credentials: $input['password'],
                verified:    true,
            );

            return $user;
        });
    }
}
```

The package does not provide Fortify's `CreatesNewUsers` implementation because the authenticatable model and user attributes are application-specific. Your app decides whether it is creating a `User`, `Admin`, or another authenticatable model, then calls `UserIdentityCreate` for the identities it needs.

---

### Email Verification — `MustVerifyEmail` Trait

[](#email-verification--mustverifyemail-trait)

The package ships a `MustVerifyEmail` trait that reads the email address and verification status from the `email` identity record rather than from user model columns:

```
// app/Models/User.php
use Chuoke\UserIdentities\Auth\MustVerifyEmail;
use Illuminate\Contracts\Auth\MustVerifyEmail as MustVerifyEmailContract;

class User extends Authenticatable implements MustVerifyEmailContract
{
    use HasIdentities, HasIdentityAuth, MustVerifyEmail;
}
```

Fortify's email verification pipeline (`VerifyEmailResponse`, `VerificationController`) works without modification. When the user clicks the verification link, call:

```
// In your email verification handler
use Chuoke\UserIdentities\Actions\UserIdentityVerify;
use Chuoke\UserIdentities\Actions\UserIdentityFindByAuthenticatable;

$identity = (new UserIdentityFindByAuthenticatable())->execute($user, 'email');

if ($identity) {
    (new UserIdentityVerify())->execute($identity);
    // Fires IdentityVerified event — hook in listeners as needed
}
```

---

### Change Password

[](#change-password)

Fortify does not ship a built-in `UpdateUserPassword` action; applications provide their own implementation of `UpdatesUserPasswords`. Keep that action in your application and call the package password action after validation:

```
use Chuoke\UserIdentities\Actions\UserIdentityPasswordUpdate;
use Chuoke\UserIdentities\Rules\CurrentPassword;
use Illuminate\Validation\Rules\Password;

Validator::make($input, [
    'current_password' => ['required', new CurrentPassword($user)],
    'password' => ['required', 'confirmed', Password::defaults()],
])->validate();

(new UserIdentityPasswordUpdate())->execute($user, $input['password']);
```

---

### Password Reset — `ResetUserPassword` Action

[](#password-reset--resetuserpassword-action)

Add `CanResetPassword` to your User model so Fortify can send reset links:

```
// app/Models/User.php
use Chuoke\UserIdentities\Auth\CanResetPassword;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;

class User extends Authenticatable implements CanResetPasswordContract
{
    use HasIdentities, HasIdentityAuth, MustVerifyEmail, CanResetPassword;
    // CanResetPassword::getEmailForPasswordReset() delegates to MustVerifyEmail
}
```

Then call the package password action from your application's reset action:

```
use Chuoke\UserIdentities\Actions\UserIdentityPasswordUpdate;
use Illuminate\Validation\Rules\Password;

Validator::make($input, [
    'password' => ['required', 'confirmed', Password::defaults()],
])->validate();

(new UserIdentityPasswordUpdate())->execute($user, $input['password']);
```

---

### Profile Information

[](#profile-information)

Fortify does not ship a built-in `UpdateUserProfileInformation` action; applications provide their own implementation of `UpdatesUserProfileInformation`. Keep that action in your application because profile fields are business-specific. When a profile update should also update an identity, call the package identity actions from your app action:

```
use Chuoke\UserIdentities\Actions\UserIdentityFindByAuthenticatable;
use Chuoke\UserIdentities\Actions\UserIdentityUpdate;
use Chuoke\UserIdentities\Dtos\UserIdentityUpdateData;

$identity = (new UserIdentityFindByAuthenticatable())->execute($user, 'email');

if ($identity && $identity->identifier !== $input['email']) {
    (new UserIdentityUpdate())->execute(
        $identity,
        UserIdentityUpdateData::forIdentifier($input['email'], false),
    );
}
```

---

### Two-Factor Authentication — `HasTwoFactorIdentity` Trait

[](#two-factor-authentication--hastwofactoridentity-trait)

Fortify 2FA is never enabled by this package. Enable Fortify's two-factor feature only in your application's `config/fortify.php` when you want it.

When enabled, `HasTwoFactorIdentity` lets Fortify 2FA work without any additional columns on the users table:

```
// app/Models/User.php
use Chuoke\UserIdentities\Concerns\HasTwoFactorIdentity;
use Laravel\Fortify\TwoFactorAuthenticatable;

class User extends Authenticatable
{
    use HasIdentities, HasIdentityAuth, HasTwoFactorIdentity, TwoFactorAuthenticatable;
}
```

Fortify's 2FA controllers type-hint Fortify's concrete action classes instead of contracts. The package 2FA actions replace those concrete classes and directly manage the `two_factor` record in `user_identities`: enabling creates or updates the identity, confirming marks it verified, disabling deletes it, and regenerating recovery codes updates its JSON credentials.

Bind them explicitly in your application's service provider:

```
use Chuoke\UserIdentities\Fortify\Actions\ConfirmTwoFactorAuthentication as IdentityConfirmTwoFactorAuthentication;
use Chuoke\UserIdentities\Fortify\Actions\DisableTwoFactorAuthentication as IdentityDisableTwoFactorAuthentication;
use Chuoke\UserIdentities\Fortify\Actions\EnableTwoFactorAuthentication as IdentityEnableTwoFactorAuthentication;
use Chuoke\UserIdentities\Fortify\Actions\GenerateNewRecoveryCodes as IdentityGenerateNewRecoveryCodes;
use Laravel\Fortify\Actions\ConfirmTwoFactorAuthentication as FortifyConfirmTwoFactorAuthentication;
use Laravel\Fortify\Actions\DisableTwoFactorAuthentication as FortifyDisableTwoFactorAuthentication;
use Laravel\Fortify\Actions\EnableTwoFactorAuthentication as FortifyEnableTwoFactorAuthentication;
use Laravel\Fortify\Actions\GenerateNewRecoveryCodes as FortifyGenerateNewRecoveryCodes;

public function register(): void
{
    $this->app->bind(FortifyEnableTwoFactorAuthentication::class, IdentityEnableTwoFactorAuthentication::class);
    $this->app->bind(FortifyConfirmTwoFactorAuthentication::class, IdentityConfirmTwoFactorAuthentication::class);
    $this->app->bind(FortifyDisableTwoFactorAuthentication::class, IdentityDisableTwoFactorAuthentication::class);
    $this->app->bind(FortifyGenerateNewRecoveryCodes::class, IdentityGenerateNewRecoveryCodes::class);
}
```

The `HasTwoFactorIdentity` trait provides Eloquent attribute accessors that intercept Fortify's access to `two_factor_secret`, `two_factor_recovery_codes`, and `two_factor_confirmed_at`, storing all data as a single identity record:

Fortify expects (user column)Stored as (identity)`two_factor_secret``type='two_factor'` credential JSON `.secret``two_factor_recovery_codes``type='two_factor'` credential JSON `.recovery_codes``two_factor_confirmed_at``type='two_factor'` identity `verified_at`The trait also listens for Fortify's `TwoFactorAuthenticationDisabled` event when Fortify is installed. This lets Fortify's default disable action remove the `two_factor` identity instead of leaving an empty identity record behind.

For a more explicit Fortify integration, the package also ships optional replacements for Fortify's concrete 2FA actions. They are not required, but they are useful when you want the action code itself to directly manage `UserIdentity` records or serve as sample code:

```
use Chuoke\UserIdentities\Fortify\Actions\ConfirmTwoFactorAuthentication as IdentityConfirmTwoFactorAuthentication;
use Chuoke\UserIdentities\Fortify\Actions\DisableTwoFactorAuthentication as IdentityDisableTwoFactorAuthentication;
use Chuoke\UserIdentities\Fortify\Actions\EnableTwoFactorAuthentication as IdentityEnableTwoFactorAuthentication;
use Chuoke\UserIdentities\Fortify\Actions\GenerateNewRecoveryCodes as IdentityGenerateNewRecoveryCodes;
use Laravel\Fortify\Actions\ConfirmTwoFactorAuthentication as FortifyConfirmTwoFactorAuthentication;
use Laravel\Fortify\Actions\DisableTwoFactorAuthentication as FortifyDisableTwoFactorAuthentication;
use Laravel\Fortify\Actions\EnableTwoFactorAuthentication as FortifyEnableTwoFactorAuthentication;
use Laravel\Fortify\Actions\GenerateNewRecoveryCodes as FortifyGenerateNewRecoveryCodes;

public function register(): void
{
    $this->app->bind(FortifyEnableTwoFactorAuthentication::class, IdentityEnableTwoFactorAuthentication::class);
    $this->app->bind(FortifyConfirmTwoFactorAuthentication::class, IdentityConfirmTwoFactorAuthentication::class);
    $this->app->bind(FortifyDisableTwoFactorAuthentication::class, IdentityDisableTwoFactorAuthentication::class);
    $this->app->bind(FortifyGenerateNewRecoveryCodes::class, IdentityGenerateNewRecoveryCodes::class);
}
```

**Zero new columns on users table. Zero new columns on identities table.**

---

### Minimal `FortifyServiceProvider` Wiring

[](#minimal-fortifyserviceprovider-wiring)

```
// app/Providers/FortifyServiceProvider.php
use Chuoke\UserIdentities\Fortify\Actions\ConfirmPassword;
use Laravel\Fortify\Fortify;

public function boot(): void
{
    Fortify::confirmPasswordsUsing(app(ConfirmPassword::class));
}
```

Socialite Integration
---------------------

[](#socialite-integration)

Bind OAuth identities from Socialite callbacks with minimal code:

```
use Chuoke\UserIdentities\Actions\UserIdentityFindOrCreateFromSocialite;
use Chuoke\UserIdentities\Actions\UserIdentitySocialiteBind;
use Chuoke\UserIdentities\Actions\AuthenticatableFindByIdentity;

// In your Socialite callback controller
public function handleCallback(string $provider)
{
    $socialiteUser = Socialite::driver($provider)->user();

    // Option 1: Find existing user or create new one
    $action = new UserIdentityFindOrCreateFromSocialite();
    [$user, $identity, $wasCreated] = $action->execute(
        $provider,
        $socialiteUser,
        fn ($su) => User::create(['name' => $su->getName()])
    );

    Auth::login($user);

    // Option 2: Bind to current authenticated user
    $identity = (new UserIdentitySocialiteBind())->execute(Auth::user(), $provider, $socialiteUser);

    // Option 3: Just look up existing user
    $user = (new AuthenticatableFindByIdentity())->execute($provider, $socialiteUser->getId());
}
```

Stored credential data includes token, refresh\_token, expires\_in, nickname, avatar, email, and name.

Sanctum Compatibility
---------------------

[](#sanctum-compatibility)

Sanctum uses its own `personal_access_tokens` table and does not add columns to the users table. It is fully compatible with this package out of the box — no special configuration needed.

If you want unified identity querying (list all auth methods including Sanctum tokens), you can register a custom `SanctumTokenIdentityType` that syncs Sanctum tokens as identities.

Migrating Existing Projects
---------------------------

[](#migrating-existing-projects)

If your project already has `email`, `password`, `remember_token` on the users table, you can migrate to this package **without downtime**.

### Step 1: Enable Fallback Mode

[](#step-1-enable-fallback-mode)

```
USER_IDENTITIES_FALLBACK=true
```

With fallback enabled, the package reads from identities when available, but gracefully falls back to user model columns (`password`, `remember_token`) when no identity record exists. Your app works exactly as before.

### Step 2: Run Data Migration

[](#step-2-run-data-migration)

```
use Chuoke\UserIdentities\Actions\MigrateUserToIdentities;

$migrator = new MigrateUserToIdentities();

// Migrate all users
$stats = $migrator->migrateAll(
    modelClass: \App\Models\User::class,
    fieldMapping: [
        'email' => 'email',          // users.email -> identity type 'email'
        // 'username' => 'username', // users.username -> identity type 'username'
    ],
    migratePassword: true,          // Copy password hash (no re-hash needed)
    migrateRememberToken: true,     // Copy remember_token
);

// $stats = ['total' => 1000, 'migrated' => 998, 'skipped' => 2]
```

The migrator copies existing password hashes directly — no re-hashing, no user disruption.

### Step 3: Disable Fallback

[](#step-3-disable-fallback)

Once all users are migrated, disable fallback:

```
USER_IDENTITIES_FALLBACK=false
```

### Step 4 (Optional): Remove Old Columns

[](#step-4-optional-remove-old-columns)

```
Schema::table('users', function (Blueprint $table) {
    $table->dropColumn(['password', 'remember_token']);
    // Keep 'email' if you want it for display/search (see Hybrid Mode below)
});
```

Hybrid Mode: Keeping Fields on Users Table
------------------------------------------

[](#hybrid-mode-keeping-fields-on-users-table)

Some fields like `email` are used very frequently for display, search, and notifications. You may want to keep them on the users table while still using identities for auth.

### Setup

[](#setup)

```
// config/user-identities.php
'sync_to_user' => [
    'email' => 'email',      // identity type 'email' identifier -> users.email
    // 'mobile' => 'phone',  // identity type 'mobile' identifier -> users.phone
],
```

```
// app/Models/User.php
use Chuoke\UserIdentities\Concerns\HasIdentities;
use Chuoke\UserIdentities\Concerns\HasIdentityAuth;
use Chuoke\UserIdentities\Concerns\SyncsIdentityFields;

class User extends Authenticatable
{
    use HasIdentities, HasIdentityAuth, SyncsIdentityFields;
}
```

### How Sync Works

[](#how-sync-works)

ActionEffectIdentity email updated`users.email` auto-synced`$user->email = 'new@...'` then `save()`Identity identifier auto-syncedManual bulk sync`$user->syncAllIdentitiesToUser()`Initial setup from user fields`$user->syncAllUserFieldsToIdentities($password)`### Recommended Architecture

[](#recommended-architecture)

```
users table:
  id, name, email, created_at, updated_at
  ↑ email kept for display/search/notifications

user_identities table:
  type='email', identifier='user@example.com', credentials='$2y$...'
  ↑ email + password for authentication
  ↑ auto-synced with users.email

```

This gives you the best of both worlds:

- ⚡ `User::where('email', ...)` works fast (no join needed)
- 🔒 Auth data is cleanly separated
- 🔄 Data stays consistent automatically

Architecture
------------

[](#architecture)

### Identity Type Hierarchy

[](#identity-type-hierarchy)

```
IdentityTypeInterface
├── PasswordBasedIdentityType
│   ├── EmailIdentityType (HashCredentialProcessor)
│   ├── MobileIdentityType (HashCredentialProcessor)
│   └── UsernameIdentityType (HashCredentialProcessor)
├── OAuthBasedIdentityType
│   ├── GithubIdentityType (PlainCredentialProcessor)
│   ├── GoogleIdentityType (PlainCredentialProcessor)
│   └── TwitterIdentityType (PlainCredentialProcessor)
└── TokenBasedIdentityType
    ├── ApiKeyIdentityType (EncryptCredentialProcessor)
    ├── JwtIdentityType (PlainCredentialProcessor)
    └── CustomJwtIdentityType (CustomJwtCredentialProcessor)

```

### Database Schema

[](#database-schema)

The package uses a single `user_identities` table, **keeping your users table clean**:

```
-- users table (lean):
-- id, name, created_at, updated_at  -- Core user data only

-- user_identities table (flexible):
-- id, authenticatable_type, authenticatable_id
-- type, identifier, credentials, verified_at
-- created_at, updated_at
```

**What lives in identities (not users table):**

CapabilityIdentity typeStored inEmail login`email`identifier + hashed passwordMobile login`mobile`identifier + hashed passwordUsername login`username`identifier + hashed passwordOAuth (GitHub, etc.)`github`provider ID + token JSONAPI key`api_key`key name + encrypted secretRemember me token`_remember_token`system token2FA secret + codes`two_factor`JSON credentials### Core Components

[](#core-components)

- **Traits**: `HasIdentities`, `HasIdentityAuth`, `HasTwoFactorIdentity`, `SyncsIdentityFields`
- **Models**: `UserIdentity` - Main model for identity records
- **Actions**: `UserIdentityCreate`, `UserIdentityFindOrCreateFromSocialite`, `UserIdentitySocialiteBind`, `UserIdentityUpdate`, `UserIdentityVerify`, `UserIdentityDelete`, `UserIdentityPasswordUpdate`, `MigrateUserToIdentities`
- **Auth**: `IdentityUserProvider` - Custom user provider with remember-me, rehash, and credential mapping support
- **Types**: Hierarchical identity type classes with built-in behaviors
- **Events**: `IdentityCreated`, `IdentityUpdated`, `IdentityDeleted`, `IdentityVerified`
- **Processors**: `HashCredentialProcessor`, `EncryptCredentialProcessor`, `PlainCredentialProcessor`

Events
------

[](#events)

All identity lifecycle operations dispatch events:

ActionEventCreate identity`IdentityCreated`Update identity`IdentityUpdated` (with `$changes` array)Delete identity`IdentityDeleted`Verify identity`IdentityVerified`Configuration
-------------

[](#configuration)

```
// config/user-identities.php
return [
    // Custom table name
    'table' => env('USER_IDENTITIES_TABLE', 'user_identities'),

    // Register identity types
    'types' => [
        'email' => EmailIdentityType::class,
        // ...
    ],

    // Require verification before login
    'require_verification' => env('USER_IDENTITIES_REQUIRE_VERIFICATION', true),

    // Password-syncable identity types
    'passwordable_types' => ['email', 'mobile', 'username'],

    // Map credential field names to identity types (Fortify/Breeze compat)
    'credential_field_mapping' => [
        'email' => 'email',
        'mobile' => 'mobile',
        'username' => 'username',
    ],

    // Fallback to user model columns when identity not found (migration support)
    'fallback_to_user_model' => env('USER_IDENTITIES_FALLBACK', false),

    // Sync identity identifiers back to user model columns
    'sync_to_user' => [
        // 'email' => 'email',
    ],
];
```

Requirements
------------

[](#requirements)

- PHP &gt;= 8.1
- Laravel &gt;= 12.0

License
-------

[](#license)

MIT

Contributing
------------

[](#contributing)

Contributions are welcome! Please feel free to submit a Pull Request.

For detailed API documentation, check the inline code comments in the source files.

###  Health Score

35

—

LowBetter than 77% of packages

Maintenance89

Actively maintained with recent releases

Popularity5

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity35

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~7 days

Total

3

Last Release

62d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/d610c8864bc961881283567007e0e0e2a78f6d69a3df114256ea78d7bcbf78da?d=identicon)[chuoke](/maintainers/chuoke)

---

Top Contributors

[![chuoke](https://avatars.githubusercontent.com/u/17611457?v=4)](https://github.com/chuoke "chuoke (11 commits)")

---

Tags

laraveluser-identities

### Embed Badge

![Health badge](/badges/chuoke-laravel-user-identities/health.svg)

```
[![Health](https://phpackages.com/badges/chuoke-laravel-user-identities/health.svg)](https://phpackages.com/packages/chuoke-laravel-user-identities)
```

###  Alternatives

[jeremy379/laravel-openid-connect

OpenID Connect support to the PHP League's OAuth2 Server. Compatible with Laravel Passport.

61437.0k9](/packages/jeremy379-laravel-openid-connect)[api-platform/laravel

API Platform support for Laravel

58174.6k17](/packages/api-platform-laravel)[ecotone/laravel

Ecotone for Laravel — CQRS, Event Sourcing, Sagas, Durable Workflows, and Outbox on top of Laravel Queue, via PHP attributes.

21318.6k3](/packages/ecotone-laravel)[aurorawebsoftware/aauth

Laravel Aauth

412.1k1](/packages/aurorawebsoftware-aauth)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
