PHPackages                             andmarruda/authmodule - 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. andmarruda/authmodule

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

andmarruda/authmodule
=====================

Invitation-based authentication module for Laravel

0.0.2(1mo ago)020PHPPHP ^8.1

Since May 1Pushed 1mo agoCompare

[ Source](https://github.com/andmarruda/laravel-modules-auth)[ Packagist](https://packagist.org/packages/andmarruda/authmodule)[ RSS](/packages/andmarruda-authmodule/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (2)Dependencies (10)Versions (3)Used By (0)

AuthModule
==========

[](#authmodule)

A self-contained Laravel module for **invitation-based user registration** with audit logging. Built with Clean Architecture (Ports &amp; Adapters), making it easy to swap implementations without touching business logic.

Features
--------

[](#features)

- **Invitation workflow** -- managers invite users by email, users register via secure token
- **Role-based access** -- only managers can create invitations
- **Audit logging** -- every action (invite, accept, register) is logged with IP, user agent, and metadata
- **Queued emails** -- invitation emails are dispatched to the queue for async delivery
- **Secure tokens** -- 64-character hex tokens generated with `random_bytes()`
- **Idempotent acceptance** -- accepting an already-accepted invitation safely returns success
- **Resource scoping** -- optional `resource_scope` field for multi-tenant or permission scenarios
- **Native teams** -- users can belong to multiple teams and teams can contain multiple users
- **Hybrid auth ready** -- session (`web`) remains default, optional `sanctum` guard can be enabled per route group
- **JWT auth ready** -- native `jwt` guard with bearer token issuance endpoint (`/auth/jwt/token`)

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

[](#architecture)

```
AuthModule/
├── Models/                  # Eloquent domain models
├── UseCases/                # Business logic (one class per use case)
│   ├── Register/
│   ├── InviteUser/
│   └── AcceptInvitation/
├── Ports/                   # Interfaces (contracts)
│   ├── Repositories/
│   └── Services/
├── Infrastructure/          # Concrete implementations
│   ├── Persistence/
│   ├── Services/
│   └── Mail/
├── Http/
│   ├── Controllers/
│   └── Routes/
├── Migrations/
├── Factories/
├── Resources/views/
└── Tests/

```

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

[](#requirements)

- PHP 8.1+
- Laravel 11+
- A configured mail driver (for sending invitations)
- A configured queue worker (invitations use `Mail::queue()`)
- Depends on `andmarruda/authorization-module` (`Authorizable`/`HasAuthorization`)

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

[](#installation)

### 1. Install via Composer

[](#1-install-via-composer)

```
composer require andmarruda/authmodule
```

### 2. Register the service provider

[](#2-register-the-service-provider)

For Laravel 11+, the provider is auto-discovered via Composer.

If auto-discovery is disabled in your app, add the provider manually in `bootstrap/providers.php`:

```
return [
    // ...
    Andmarruda\AuthModule\AuthModuleServiceProvider::class,
];
```

The service provider automatically:

- Binds all interfaces to their Eloquent/Mail implementations
- Loads routes, migrations, and views

### 3. Run migrations

[](#3-run-migrations)

```
php artisan migrate
```

This creates module tables such as `invitations`, `teams`, `team_user`, `team_invitations`, `auth_audit_logs`, `otps`, and `user_preferences`, plus updates `users` when needed.

> **Note:** The module ships its own `users` table migration. If your project already has one, remove or adjust the module's `2026_02_15_100000_create_users_table.php` migration to avoid conflicts. **Upgrade note:** If you already applied an older `team_invitations` migration with `invited_by`, run the package upgrade migration (`2026_02_20_101100_migrate_team_invitations_to_morphable_inviter.php`) to migrate to `inviter_type`/`inviter_id` and remove the legacy column.

### 4. Configure the invitation URL

[](#4-configure-the-invitation-url)

Invitation emails include a link pointing to your frontend. Set the base URL in your `.env`:

```
FRONTEND_URL=https://yourapp.com
```

The generated link format is: `{FRONTEND_URL}/invitations/accept?token={TOKEN}`

Falls back to `APP_URL` if `FRONTEND_URL` is not set.

### 5. Configure mail and queue

[](#5-configure-mail-and-queue)

Make sure your mail driver and queue worker are properly configured so invitation emails are sent:

```
# .env
MAIL_MAILER=smtp
QUEUE_CONNECTION=database   # or redis, sqs, etc.
```

```
php artisan queue:work
```

Usage
-----

[](#usage)

### API Endpoints

[](#api-endpoints)

MethodURIDescriptionAuth`POST``/invitations/create`Create an invitationYes (manager only)`POST``/invitations/accept`Accept an invitationNo`POST``/users/register`Register via invitation tokenNo`POST``/auth/jwt/token`Issue JWT token from email/passwordNo`POST``/teams`Create a new teamYes`GET``/teams/mine`List current user teamsYes`POST``/teams/invitations/create`Invite user to teamYes`GET``/teams/invitations/resolve?token=...`Resolve invitation and detect account existenceNo`POST``/teams/invitations/redeem`Redeem invitation (existing user path)Optional auth`POST``/teams/invitations/register`Register from team invitation (new user path)No`GET``/auth/social/{provider}/redirect`Start OAuth login (`google`, `github`)No`GET``/auth/social/{provider}/callback`OAuth callbackNo`GET``/auth/social/profile/status`Get missing profile fields after social authYes`POST``/auth/social/profile/complete`Complete missing profile dataYes### Create an invitation (manager only)

[](#create-an-invitation-manager-only)

```
POST /invitations/create
Content-Type: application/json
Authorization: Bearer {token}

{
  "email": "newuser@example.com",
  "resource_scope": "project-42"  // optional
}
```

**Responses:**

- `201` -- Invitation created, email queued
- `403` -- Authenticated user is not a manager
- `422` -- Validation error or email already registered

### Accept an invitation

[](#accept-an-invitation)

```
POST /invitations/accept
Content-Type: application/json

{
  "token": "a1b2c3d4..."
}
```

**Responses:**

- `200` -- Invitation accepted (idempotent)
- `404` -- Token not found
- `410` -- Invitation expired

### Register a new user

[](#register-a-new-user)

```
POST /users/register
Content-Type: application/json

{
  "token": "a1b2c3d4...",
  "name": "Jane Doe",
  "password": "SecurePass123!",
  "password_confirmation": "SecurePass123!"
}
```

**Responses:**

- `201` -- User created
- `404` -- Token not found
- `410` -- Invitation expired or already used

### Typical flow

[](#typical-flow)

```
1. Manager  ->  POST /invitations/create  { email: "jane@co.com" }
                  Module generates token, queues email

2. Jane       POST /invitations/accept  { token: "abc123..." }
                  Marks invitation as accepted

4. Frontend ->  POST /users/register  { token: "abc123...", name: "Jane", password: "..." }
                  Creates the user account

```

### Social login (Google/GitHub)

[](#social-login-googlegithub)

This package supports `google` and `github` with Laravel Socialite.

1. Add provider credentials to your `.env`:

```
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
GOOGLE_REDIRECT_URI="${APP_URL}/auth/social/google/callback"

GITHUB_CLIENT_ID=...
GITHUB_CLIENT_SECRET=...
GITHUB_REDIRECT_URI="${APP_URL}/auth/social/github/callback"
```

2. Configure `config/services.php` in your Laravel app:

```
'google' => [
    'client_id' => env('GOOGLE_CLIENT_ID'),
    'client_secret' => env('GOOGLE_CLIENT_SECRET'),
    'redirect' => env('GOOGLE_REDIRECT_URI'),
],

'github' => [
    'client_id' => env('GITHUB_CLIENT_ID'),
    'client_secret' => env('GITHUB_CLIENT_SECRET'),
    'redirect' => env('GITHUB_REDIRECT_URI'),
],
```

3. (Optional) publish and tune package config:

```
php artisan vendor:publish --tag=authmodule-config
```

`config/authmodule.php` lets you customize allowed providers, scopes, and post-login/error redirects.

For onboarding after social login, configure:

- `authmodule.profile.required_user_fields`
- `authmodule.profile.required_preference_keys`
- `authmodule.profile.redirect_to_onboarding`

4. Manual test flow:

```
GET /auth/social/google/redirect
-> provider consent screen
-> /auth/social/google/callback
-> user is created/linked and authenticated
-> if profile is incomplete, redirected to onboarding path

```

5. (Optional) Protect app routes until profile is complete:

```
Route::middleware(['auth', 'authmodule.profile.complete'])->group(function () {
    Route::get('/dashboard', fn () => 'ok');
});
```

Creating a manager
------------------

[](#creating-a-manager)

The first manager must be created manually (via tinker, a seeder, or a direct DB update):

```
php artisan tinker
```

```
use Andmarruda\AuthModule\Models\User;

User::create([
    'name'       => 'Admin',
    'email'      => 'admin@example.com',
    'password'   => 'your-secure-password',  // automatically hashed via cast
    'is_manager' => true,
]);
```

From there, managers can invite other users through the API.

Customization
-------------

[](#customization)

### Session + Sanctum

[](#session--sanctum)

The package supports `web` (session) and `sanctum` (API token) at the same time.

Publish config and set guards per route group:

```
php artisan vendor:publish --tag=authmodule-config
```

```
'auth' => [
    'default_guard' => 'web',
    'session_guard' => 'web',
    'api_guard' => 'sanctum', // optional
    'invitation_create_guards' => ['web', 'sanctum'],
    'social_profile_guards' => ['web', 'sanctum'],
    'preferences_guards' => ['web', 'sanctum'],
    'teams_guards' => ['web', 'sanctum'],
],
'teams' => [
    'inviter_models' => [
        'user' => \Andmarruda\AuthModule\Models\User::class,
        'tenant' => \App\Models\Tenant::class, // optional
    ],
    'inviter_authorizer' => \Andmarruda\AuthModule\Support\DefaultTeamInvitationInviterAuthorizer::class,
],
```

By default, protected endpoints accept both session (`web`) and API token (`sanctum`) authentication. If you enable `sanctum` guards, install/configure Sanctum in the host app. When creating team invitations, send `inviter_type` (`user`/`tenant`) and `inviter_id` if you want a non-user inviter context. The default authorizer only allows the authenticated user to be the inviter; provide your own authorizer class to validate tenant contexts.

### JWT (native)

[](#jwt-native)

You can also use the built-in `jwt` guard for bearer authentication. Default algorithm is `RS256` (recommended for multi-client/mobile/public API scenarios). `EdDSA` (Ed25519) is also supported when `libsodium` is available.

Configuration keys:

```
'jwt' => [
    'algorithm' => env('AUTHMODULE_JWT_ALGORITHM', 'RS256'),
    'secret' => env('AUTHMODULE_JWT_SECRET', env('APP_KEY', '')),
    'private_key' => env('AUTHMODULE_JWT_PRIVATE_KEY', ''),
    'public_key' => env('AUTHMODULE_JWT_PUBLIC_KEY', ''),
    'private_key_passphrase' => env('AUTHMODULE_JWT_PRIVATE_KEY_PASSPHRASE', ''),
    'key_id' => env('AUTHMODULE_JWT_KEY_ID', ''),
    'ttl_minutes' => (int) env('AUTHMODULE_JWT_TTL_MINUTES', 60),
    'issuer' => env('AUTHMODULE_JWT_ISSUER', env('APP_URL', 'authmodule')),
    'leeway_seconds' => (int) env('AUTHMODULE_JWT_LEEWAY_SECONDS', 0),
],
```

Minimal `.env` for `RS256`:

```
AUTHMODULE_JWT_ALGORITHM=RS256
AUTHMODULE_JWT_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
AUTHMODULE_JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"
AUTHMODULE_JWT_KEY_ID=primary-rsa-key-2026
```

For `EdDSA`, set `AUTHMODULE_JWT_ALGORITHM=EdDSA` and provide base64 keys:

```
AUTHMODULE_JWT_PRIVATE_KEY=base64:...
AUTHMODULE_JWT_PUBLIC_KEY=base64:...
```

Token endpoint:

```
POST /auth/jwt/token
Content-Type: application/json

{
  "email": "user@example.com",
  "password": "your-password"
}
```

Generate EdDSA keys + ready-to-use env file:

```
php artisan authmodule:jwt:eddsa-keys
```

Useful options:

```
php artisan authmodule:jwt:eddsa-keys --path=storage/app/authmodule/jwt --env-file=.env.authmodule.jwt --force
php artisan authmodule:jwt:eddsa-keys --stdout
```

The command creates:

- `eddsa-private.key.b64`
- `eddsa-public.key.b64`
- env variables for `AUTHMODULE_JWT_ALGORITHM`, `AUTHMODULE_JWT_PRIVATE_KEY`, `AUTHMODULE_JWT_PUBLIC_KEY`, `AUTHMODULE_JWT_KEY_ID`, and `AUTHMODULE_JWT_TTL_MINUTES`.

If `ext-sodium` is not available, the command exits with guidance to install/enable it.

### Swapping implementations

[](#swapping-implementations)

The module uses interface bindings, so you can replace any implementation. Override the bindings in your own service provider:

```
use Andmarruda\AuthModule\Ports\Services\InvitationMailerInterface;
use App\CustomInvitationMailer;

public function register(): void
{
    $this->app->bind(InvitationMailerInterface::class, CustomInvitationMailer::class);
}
```

Available interfaces:

InterfaceDefault ImplementationPurpose`UserRepositoryInterface``EloquentUserRepository`User persistence`InvitationRepositoryInterface``EloquentInvitationRepository`Invitation persistence`TokenGeneratorInterface``SecureTokenGenerator`Token generation`AuditLoggerInterface``EloquentAuditLogger`Audit logging`InvitationMailerInterface``MailInvitationMailer`Sending invitation emails### Publishing views

[](#publishing-views)

To customize the invitation email template, copy the view to your project's resources:

```
mkdir -p resources/views/vendor/authmodule/emails
cp app/Modules/AuthModule/Resources/views/emails/invitation.blade.php \
   resources/views/vendor/authmodule/emails/invitation.blade.php
```

Laravel will automatically use the vendor override.

Testing
-------

[](#testing)

The module includes both unit and feature tests.

### Running tests

[](#running-tests)

```
./vendor/bin/phpunit
```

### Test coverage

[](#test-coverage)

**Unit tests** (mocked dependencies):

- `RegisterUserTest` -- registration with valid/invalid/expired/used tokens
- `InviteUserTest` -- manager permissions, duplicate email checks, resource scoping
- `AcceptInvitationTest` -- acceptance, expiration, idempotency

**Feature tests** (full HTTP with database):

- `UserControllerTest` -- registration endpoint, validation, audit log creation
- `InvitationControllerTest` -- invitation creation, acceptance, mail dispatch, auth guards

### Using factories in your own tests

[](#using-factories-in-your-own-tests)

```
use Andmarruda\AuthModule\Models\User;
use Andmarruda\AuthModule\Models\Invitation;

// Create a manager
$manager = User::factory()->manager()->create();

// Create a pending invitation
$invitation = Invitation::factory()->create(['invited_by' => $manager->id]);

// Create an expired invitation
$expired = Invitation::factory()->expired()->create();

// Create an already-accepted invitation
$accepted = Invitation::factory()->accepted()->create();
```

Database schema
---------------

[](#database-schema)

### `users`

[](#users)

ColumnTypeNotes`id`bigintPK`name`string`email`stringunique`password`stringhashed`is_manager`booleandefault `false``email_verified_at`timestampnullable`remember_token`stringnullable`created_at` / `updated_at`timestamps### `invitations`

[](#invitations)

ColumnTypeNotes`id`bigintPK`email`stringindexed with `accepted_at``token`string(64)unique`invited_by`FK -&gt; userscascade on delete`resource_scope`stringnullable`expires_at`timestampdefault: 7 days from creation`accepted_at`timestampnullable`created_at` / `updated_at`timestamps### `auth_audit_logs`

[](#auth_audit_logs)

ColumnTypeNotes`id`bigintPK`action`string`invitation_created`, `invitation_accepted`, `user_registered``actor_id`FK -&gt; usersnullable, null on delete`actor_email`stringnullable`target_email`stringnullable`invitation_id`FK -&gt; invitationsnullable, null on delete`resource_scope`stringnullable`metadata`jsonnullable`ip_address`stringnullable`user_agent`textnullable`created_at`timestamp### `teams`

[](#teams)

ColumnTypeNotes`id`bigintPK`name`string`slug`stringunique`owner_id`FK -&gt; userscascade on delete`created_at` / `updated_at`timestamps### `team_user`

[](#team_user)

ColumnTypeNotes`id`bigintPK`team_id`FK -&gt; teamscascade on delete`user_id`FK -&gt; userscascade on delete`role`stringdefault `member``joined_at`timestampnullable`created_at` / `updated_at`timestamps### `team_invitations`

[](#team_invitations)

ColumnTypeNotes`id`bigintPK`team_id`FK -&gt; teamscascade on delete`email`stringindexed with `accepted_at``token`string(64)unique`inviter_type`stringmorph type (`User`, `Tenant`, etc.)`inviter_id`bigintmorph id`role`stringdefault `member``expires_at`timestamp`accepted_at`timestampnullable`created_at` / `updated_at`timestampsLicense
-------

[](#license)

This module is part of the Novos Horizontes project.

###  Health Score

36

—

LowBetter than 79% of packages

Maintenance90

Actively maintained with recent releases

Popularity9

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity34

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 ~68 days

Total

2

Last Release

48d ago

### Community

Maintainers

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

---

Top Contributors

[![andmarruda](https://avatars.githubusercontent.com/u/29872593?v=4)](https://github.com/andmarruda "andmarruda (1 commits)")

### Embed Badge

![Health badge](/badges/andmarruda-authmodule/health.svg)

```
[![Health](https://phpackages.com/badges/andmarruda-authmodule/health.svg)](https://phpackages.com/packages/andmarruda-authmodule)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3345.4M354](/packages/psalm-plugin-laravel)[laravel/pulse

Laravel Pulse is a real-time application performance monitoring tool and dashboard for your Laravel application.

1.7k16.3M158](/packages/laravel-pulse)[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9922.4M147](/packages/roots-acorn)[api-platform/laravel

API Platform support for Laravel

58190.1k21](/packages/api-platform-laravel)[illuminate/notifications

The Illuminate Notifications package.

483.1M1.2k](/packages/illuminate-notifications)[alajusticia/laravel-logins

Session management in Laravel apps, user notifications on new access, support for multiple separate remember tokens, IP geolocation, User-Agent parser

2115.6k](/packages/alajusticia-laravel-logins)

PHPackages © 2026

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