PHPackages                             cesargb/laravel-magiclink - 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. cesargb/laravel-magiclink

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

cesargb/laravel-magiclink
=========================

Create secure link for access to private data or login in Laravel without password

v2.27.0(2mo ago)4571.3M↓14.3%51[1 issues](https://github.com/cesargb/laravel-magiclink/issues)MITPHPPHP ^8.2CI passing

Since Jul 25Pushed 1mo ago5 watchersCompare

[ Source](https://github.com/cesargb/laravel-magiclink)[ Packagist](https://packagist.org/packages/cesargb/laravel-magiclink)[ Docs](https://github.com/cesargb/laravel-magiclink)[ RSS](/packages/cesargb-laravel-magiclink/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (10)Dependencies (8)Versions (67)Used By (0)Security (1)

[![Banner MagicLink](art/banner-laravel-magiclink.webp)](art/banner-laravel-magiclink.webp)

[![Latest Version on Packagist](https://camo.githubusercontent.com/48118aa80d2641642b8adebc934769b6a95123dc188c9ffa3fbcfb77fc05688e/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f636573617267622f6c61726176656c2d6d616769636c696e6b2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/cesargb/laravel-magiclink)[![tests](https://github.com/cesargb/laravel-magiclink/workflows/tests/badge.svg)](https://github.com/cesargb/laravel-magiclink/workflows/tests/badge.svg)[![static analysis](https://github.com/cesargb/laravel-magiclink/actions/workflows/static-analysis.yml/badge.svg)](https://github.com/cesargb/laravel-magiclink/actions/workflows/static-analysis.yml)[![lint](https://github.com/cesargb/laravel-magiclink/actions/workflows/lint.yml/badge.svg)](https://github.com/cesargb/laravel-magiclink/actions/workflows/lint.yml)[![Quality Score](https://camo.githubusercontent.com/fa236874262e4fa5b9267f2fd111afa096309c5611ab58d63698b29666d48afb/68747470733a2f2f696d672e736869656c64732e696f2f7363727574696e697a65722f672f636573617267622f6c61726176656c2d6d616769636c696e6b2e7376673f7374796c653d666c61742d737175617265)](https://scrutinizer-ci.com/g/cesargb/laravel-magiclink)[![Total Downloads](https://camo.githubusercontent.com/7683e171e99f1af73fa32e9d4f525c60597617e0290e17a8f4eb82af71775015/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f636573617267622f6c61726176656c2d6d616769636c696e6b2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/cesargb/laravel-magiclink)

MagicLink for Laravels App
==========================

[](#magiclink-for-laravels-app)

Through the `MagicLink` class we can create a secure link that later being visited will perform certain actions, which will allow us offer secure content and even log in to the application.

Warning

Migrate actions is required if you are upgrading from version 2.24.2 or earlier. Please see the [Migrate actions](#migrate-actions) section for detailed instructions.

Contents
--------

[](#contents)

- [Installation](#installation)
- [Use case](#use-case)
- [Create a MagicLink](#create-a-magiclink)
- [Actions](#actions)
    - [Login](#login-action)
    - [Download file](#download-file-action)
    - [View](#view-action)
    - [Http Response](#http-response-action)
    - [Controller](#controller-action)
    - [Custom Action](#custom-action)
        - [Allowed Classes](#allowed-classes-for-custom-actions)
- [Protect with an access code](#protect-with-an-access-code)
- [Lifetime](#lifetime)
- [Events](#events)
- [Customization](#customization)
- [Rate limiting](#rate-limiting)
- [Delete Expired MagicLinks](#delete-expired-magiclinks)
- [Migrate actions](#migrate-actions)
- [Testing](#testing)
- [Contributing](#contributing)
- [Security](#security)

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

[](#installation)

You can install this package via composer using:

```
composer require cesargb/laravel-magiclink
```

### Preparing the database

[](#preparing-the-database)

You need to publish the migration to create the `magic_links` table:

```
php artisan vendor:publish --provider="MagicLink\MagicLinkServiceProvider" --tag="migrations"
```

After that, you need to run migrations.

```
php artisan migrate
```

Use case
--------

[](#use-case)

With this example you can create a link to auto login on your application with the desired user:

```
use MagicLink\Actions\LoginAction;
use MagicLink\MagicLink;

$urlToAutoLogin =  MagicLink::create(new LoginAction($user))->url
```

Create a MagicLink
------------------

[](#create-a-magiclink)

The `MagicLink` class has the `create` method to generate a class that through the `url` property we will obtain the link that we will send to our visitor.

This method requires the action to be performed.

Actions
-------

[](#actions)

Each MagicLink is associated with an action, which is what will be performed once the link is visited.

- [Login Action](#login-action)
- [Download file Action](#download-file-action)
- [View Action](#view-action)
- [Http Response Action](#http-response-action)
- [Http Response](#http-response-action)
- [Controller](#controller-action)
- [Custom Action](#custom-action)
- [Custom Base URL](#custom-base-url)

### Login Action

[](#login-action)

Through the `LoginAction` action, you can log in to the application using the generated link by `MagicLink`.

Your constructor supports the user who will login. Optionally we can specify the [HTTP response](https://laravel.com/docs/master/responses) using the method `response` or specify other guard with method `guard`.

Examples:

```
use MagicLink\Actions\LoginAction;
use MagicLink\MagicLink;

// Sample 1; Login and redirect to dash board
$action = new LoginAction(User::first());
$action->response(redirect('/dashboard'));

$urlToDashBoard = MagicLink::create($action)->url;

// Sample 2; Login and view forms to password reset and use guard web
$action = new LoginAction(User::first());
$action->response(view('password.reset', ['email' => 'user@example.tld']));

$urlShowView = MagicLink::create($action)->url;

// Sample 3; Login in other guard and redirect default
$action = new LoginAction(User::first());
$action->guard('customguard');

$urlShowView = MagicLink::create($action)->url;

// Sample 4; Login and remember me
$action = new LoginAction(User::first());
$action->remember();

$urlShowView = MagicLink::create($action)->url;
```

### Download file Action

[](#download-file-action)

This action, `DownloadFileAction`, permit create a link to download a private file.

The constructor require the file path.

Example:

```
use MagicLink\Actions\DownloadFileAction;
use MagicLink\MagicLink;

// Url to download the file storage_app('private_document.pdf')
$url = MagicLink::create(new DownloadFileAction('private_document.pdf'))->url;

// Download file with other file_name
$action = new DownloadFileAction('private_document.pdf', 'your_document.pdf');
$url = MagicLink::create($action)->url;

// Download file from other disk
$action = new DownloadFileAction('private_document.pdf')->disk('ftp');
$url = MagicLink::create($action)->url;
```

### View Action

[](#view-action)

With the action `ViewAction`, you can provide access to the view. You can use in his constructor the same arguments than method `view` of Laravel.

Example:

```
use MagicLink\Actions\ViewAction;
use MagicLink\MagicLink;

// Url to view a internal.blade.php
$url = MagicLink::create(new ViewAction('internal', [
    'data' => 'Your private custom content',
]))->url;
```

### Http Response Action

[](#http-response-action)

Through the `ResponseAction` action we can access private content without need login. Its constructor accepts as argument the [HTTP response](https://laravel.com/docs/responses)which will be the response of the request.

Examples:

```
use MagicLink\Actions\ResponseAction;
use MagicLink\MagicLink;

$action = new ResponseAction(function () {
    Auth::login(User::first());

    return redirect('/change_password');
});

$urlToCustomFunction = MagicLink::create($action)->url;
```

### Controller Action

[](#controller-action)

`MagicLink` can directly call a controller via the `ControllerAction` action.

The constructor requires one argument, the name of the controller class. With the second argument can call any controller method, by default it will use the `__invoke` method.

```
use MagicLink\Actions\ControllerAction;
use MagicLink\MagicLink;

// Call the method __invoke of the controller
$url = MagicLink::create(new ControllerAction(MyController::class))->url;

// Call the method show of the controller
$url = MagicLink::create(new ControllerAction(MyController::class, 'show'))->url;
```

### Custom Action

[](#custom-action)

You can create your own action class, for them you just need to extend with `MagicLink\Actions\ActionAbstract`

```
use MagicLink\Actions\ActionAbstract;

class MyCustomAction extends ActionAbstract
{
    public function __construct(public int $variable)
    {
    }

    public function run()
    {
        // Do something

        return response()->json([
            'success' => true,
            'data' => $this->variable,
        ]);
    }
}
```

You can now generate a Magiclink with the custom action

```
use MagicLink\MagicLink;

$action = new MyCustomAction('Hello world');

$urlToCustomAction = MagicLink::create($action)->url;
```

#### Allowed Classes for Custom Actions

[](#allowed-classes-for-custom-actions)

Tip

**Best Practice:** We recommend using primitive types (`int`, `string`, `array`, etc.) as properties instead of objects. Extract the necessary data from objects in the constructor and store only the primitive values.

For example, `LoginAction` receives a `User` object but stores only the `authIdentifier` (int/string) instead of the entire object. This approach is more secure and doesn't require configuring allowed classes.

When your custom action **must** contain object properties (like Eloquent models), you must explicitly allow those classes to be deserialized for security reasons. This prevents potential security vulnerabilities from arbitrary object instantiation.

**Recommended approach (using primitive types):**

```
use MagicLink\Actions\ActionAbstract;
use App\Models\User;

class SendWelcomeEmailAction extends ActionAbstract
{
    public function __construct(public int $userId, public string $email)
    {
    }

    public function run()
    {
        $user = User::find($this->userId);
        // Send email to $user
        return response('Email sent!');
    }
}

// Usage
$action = new SendWelcomeEmailAction($user->id, $user->email);
MagicLink::create($action);
```

**Alternative approach (using objects, requires configuration):**

```
use MagicLink\Actions\ActionAbstract;
use App\Models\User;

class SendWelcomeEmailAction extends ActionAbstract
{
    public function __construct(public User $user)
    {
    }

    public function run()
    {
        // Send email to $this->user
        return response('Email sent!');
    }
}
```

To allow the `User` class to be deserialized, configure it in `config/magiclink.php`:

```
'allowed_classes' => [
    \App\Models\User::class,
    \App\Models\Post::class,
    // Add any other classes your actions might use
],
```

Or use the `MAGICLINK_ALLOWED_CLASSES` environment variable with comma-separated class names:

```
MAGICLINK_ALLOWED_CLASSES="App\Models\User,App\Models\Post"
```

Warning

If you use object properties in your custom actions without configuring allowed classes, you'll get a `TypeError` when the MagicLink is accessed. Always add your classes to the allowed list.

### Custom Base URL

[](#custom-base-url)

To set the base URL for a magic link, you can use the `baseUrl` method. This method ensures that the provided base URL has a trailing slash, making it ready for URL generation.

```
$magiclink = MagicLink::create($action);

$magiclink->baseUrl("http://example.com");

$urlShowView = $magiclink->url; // http://example.com/magiclink/...
```

Protect with an access code
---------------------------

[](#protect-with-an-access-code)

Optionally you can protect the resources with an access code. You can set the access code with method `protectWithAccessCode`which accepts an argument with the access code.

```
$magiclink = MagicLink::create(new DownloadFileAction('private_document.pdf'));

$magiclink->protectWithAccessCode('secret');

$urlToSend = $magiclink->url;
```

### Custom view for access code

[](#custom-view-for-access-code)

You can customize the view of the access code form with the config file `magiclink.php`:

```
'access_code' => [
    'view' => 'magiclink::access-code', // Change with your view
],
```

This is the [default view](/resources/views/ask-for-access-code-form.blade.php)

Lifetime
--------

[](#lifetime)

By default a link will be available for 72 hours after your creation. We can modify the life time in minutes of the link by the `$lifetime` option available in the `create` method. This argument accepts the value `null` so that it does not expire in time.

```
$lifetime = 60; // 60 minutes

$magiclink = MagicLink::create(new ResponseAction(), $lifetime);

$urlToSend = $magiclink->url;
```

We also have another option `$numMaxVisits`, with which we can define the number of times the link can be visited, `null` by default indicates that there are no visit limits.

```
$lifetime = null; // not expired in the time
$numMaxVisits = 1; // Only can visit one time

$magiclink = MagicLink::create(new ResponseAction(), $lifetime, $numMaxVisits);

$urlToSend = $magiclink->url;
```

Events
------

[](#events)

MagicLink can fires three events:

### MagicLinkWasCreated

[](#magiclinkwascreated)

Event `MagicLink\Events\MagicLinkWasCreated`

This event is fired when a magic link is created.

### MagicLinkWasVisited

[](#magiclinkwasvisited)

Event `MagicLink\Events\MagicLinkWasVisited`

This event is fired when a magic link is visited.

### MagicLinkWasDeleted

[](#magiclinkwasdeleted)

Event `MagicLink\Events\MagicLinkWasDeleted`

This event is fired when an expired magic link is deleted individually. To enable it, you need to enable deletion on creation in your `.env` file:

```
# Enable deletion of expired magic links when creating new ones
MAGICLINK_DELETE_EXPIRED_WHEN_CREATED=true
```

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

[](#customization)

### Config

[](#config)

To custom this package you can publish the config file:

```
php artisan vendor:publish --provider="MagicLink\MagicLinkServiceProvider" --tag="config"
```

And edit the file `config/magiclink.php`

By default, when creating a new MagicLink, the system automatically deletes expired magic links from the database. You can disable this behavior by setting the `delete_expired_when_created` configuration to `false`:

```
// config/magiclink.php
'delete_expired_when_created' => env('MAGICLINK_DELETE_EXPIRED_WHEN_CREATED', true),
```

Or in your `.env` file:

```
MAGICLINK_DELETE_EXPIRED_WHEN_CREATED=false
```

Tip

If you disable automatic cleanup on creation, consider using Laravel's [Model Pruning](#automatic-pruning-of-expired-magiclinks) to periodically clean up expired links.

### Migrations

[](#migrations)

To customize the migration files of this package you need to publish the migration files:

```
php artisan vendor:publish --provider="MagicLink\MagicLinkServiceProvider" --tag="migrations"
```

You'll find the published files in `database/migrations/*`

### Custom response when magiclink is invalid

[](#custom-response-when-magiclink-is-invalid)

When the magicLink is invalid by default the http request return a status 403. You can custom this response with config `magiclink.invalid_response`.

#### Response

[](#response)

To return a response, use class `MagicLink\Responses\Response::class`same `response()`, you can send the arguments with options

Example:

```
    'invalid_response' => [
        'class'   => MagicLink\Responses\Response::class,
        'options' => [
            'content' => 'forbidden',
            'status' => 403,
        ],
    ],
```

#### Abort

[](#abort)

To return a exception and let the framework handle the response, use class `MagicLink\Responses\AbortResponse::class`. Same `abort()`, you can send the arguments with options.

Example:

```
    'invalid_response' => [
        'class'   => MagicLink\Responses\AbortResponse::class,
        'options' => [
            'message' => 'You Shall Not Pass!',
            'status' => 403,
        ],
    ],
```

#### Redirect

[](#redirect)

Define class `MagicLink\Responses\RedirectResponse::class` to return a `redirect()`

```
    'invalid_response' => [
        'class'   => MagicLink\Responses\RedirectResponse::class,
        'options' => [
            'to' => '/not_valid_path',
            'status' => 301,
        ],
    ],
```

#### View

[](#view)

Define class `MagicLink\Responses\ViewResponse::class` to return a `view()`

```
    'invalid_response' => [
        'class'   => MagicLink\Responses\ViewResponse::class,
        'options' => [
            'view' => 'invalid',
            'data' => [],
        ],
    ],
```

Rate limiting
-------------

[](#rate-limiting)

You can limit the number of requests per minute for a magic link. To do this, you need to set the `MAGICLINK_RATE_LIMIT` environment variable to the desired value.

By default, the rate limit is disable with value 'none', but you can set a value to limit the requests. For example, to limit the requests to 100 per minute, set

```
# .env

MAGICLINK_RATE_LIMIT=100
```

Delete Expired MagicLinks
-------------------------

[](#delete-expired-magiclinks)

MagicLink uses Laravel's [Model Pruning](https://laravel.com/docs/eloquent#pruning-models) feature through the `MassPrunable` trait to automatically clean up expired links.

You can prune expired MagicLinks by running the following Artisan command:

```
php artisan model:prune MagicLink\\MagicLink
```

You can also enable automatic cleanup of expired MagicLinks every time a new one is created by setting `MAGICLINK_DELETE_EXPIRED_WHEN_CREATED` to `true` in the env file:

```
# .env

MAGICLINK_DELETE_EXPIRED_WHEN_CREATED=true
```

Migrate actions
---------------

[](#migrate-actions)

Warning

The action storage mechanism changed from PHP serialization to HMAC-signed JSON format for improved security. If you're upgrading from version 2.24.2, you need to migrate existing MagicLinks.

To migrate legacy serialized actions to the new format, run:

```
php artisan magiclink:migrate --dry-run
```

This command will simulate the migration and display the number of legacy MagicLinks found.

If everything looks good, run the migration for real with:

```
php artisan magiclink:migrate
```

Testing
-------

[](#testing)

Run the tests with:

```
composer test
```

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

[](#contributing)

Please see [CONTRIBUTING](CONTRIBUTING.md) for details.

Security
--------

[](#security)

If you discover any security-related issues, please email instead of using the issue tracker.

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

71

—

ExcellentBetter than 100% of packages

Maintenance88

Actively maintained with recent releases

Popularity61

Solid adoption and visibility

Community27

Small or concentrated contributor base

Maturity90

Battle-tested with a long release history

 Bus Factor1

Top contributor holds 89.8% 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 ~49 days

Recently: every ~6 days

Total

64

Last Release

83d ago

Major Versions

v1.x-dev → v2.0.02020-02-02

v2.12.2 → v3.x-dev2022-09-25

PHP version history (10 changes)v1.0.0PHP &gt;=7.0

v1.1.0PHP ^7.0

v1.4.0PHP ^7.1

v2.6.0PHP ^7.2

v2.7.0PHP ^7.2 || ^8.0

v2.8.1PHP ^7.3 || ^8.0

v2.10.0PHP ^7.4 || ^8.0

v2.15.0PHP ^8.0

v2.22.0PHP ^8.1

v2.24.2PHP ^8.2

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/25681494?v=4)[Cesar Garcia](/maintainers/cesargb)[@cesargb](https://github.com/cesargb)

---

Top Contributors

[![cesargb](https://avatars.githubusercontent.com/u/25681494?v=4)](https://github.com/cesargb "cesargb (380 commits)")[![thesubhendu](https://avatars.githubusercontent.com/u/16435611?v=4)](https://github.com/thesubhendu "thesubhendu (8 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (7 commits)")[![robindrost](https://avatars.githubusercontent.com/u/588012?v=4)](https://github.com/robindrost "robindrost (5 commits)")[![benlumley](https://avatars.githubusercontent.com/u/194052?v=4)](https://github.com/benlumley "benlumley (4 commits)")[![iredmedia](https://avatars.githubusercontent.com/u/584038?v=4)](https://github.com/iredmedia "iredmedia (3 commits)")[![vanushwashere](https://avatars.githubusercontent.com/u/24995381?v=4)](https://github.com/vanushwashere "vanushwashere (2 commits)")[![rabol](https://avatars.githubusercontent.com/u/1177191?v=4)](https://github.com/rabol "rabol (2 commits)")[![saulens22](https://avatars.githubusercontent.com/u/9000854?v=4)](https://github.com/saulens22 "saulens22 (1 commits)")[![savander](https://avatars.githubusercontent.com/u/2012010?v=4)](https://github.com/savander "savander (1 commits)")[![szepeviktor](https://avatars.githubusercontent.com/u/952007?v=4)](https://github.com/szepeviktor "szepeviktor (1 commits)")[![asugai](https://avatars.githubusercontent.com/u/2228791?v=4)](https://github.com/asugai "asugai (1 commits)")[![zupolgec](https://avatars.githubusercontent.com/u/161318?v=4)](https://github.com/zupolgec "zupolgec (1 commits)")[![evokelektrique](https://avatars.githubusercontent.com/u/53313989?v=4)](https://github.com/evokelektrique "evokelektrique (1 commits)")[![jamesdb](https://avatars.githubusercontent.com/u/6299056?v=4)](https://github.com/jamesdb "jamesdb (1 commits)")[![jansgescheit](https://avatars.githubusercontent.com/u/21027386?v=4)](https://github.com/jansgescheit "jansgescheit (1 commits)")[![khalid3bdallah](https://avatars.githubusercontent.com/u/6986761?v=4)](https://github.com/khalid3bdallah "khalid3bdallah (1 commits)")[![laravel-shift](https://avatars.githubusercontent.com/u/15991828?v=4)](https://github.com/laravel-shift "laravel-shift (1 commits)")[![Marcus-IXAU](https://avatars.githubusercontent.com/u/62982527?v=4)](https://github.com/Marcus-IXAU "Marcus-IXAU (1 commits)")[![peterquentin](https://avatars.githubusercontent.com/u/1672096?v=4)](https://github.com/peterquentin "peterquentin (1 commits)")

---

Tags

authauthenticationautolaravellinkloginpasswordlesslaravellinkauthfilelogindownload

###  Code Quality

TestsPHPUnit

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/cesargb-laravel-magiclink/health.svg)

```
[![Health](https://phpackages.com/badges/cesargb-laravel-magiclink/health.svg)](https://phpackages.com/packages/cesargb-laravel-magiclink)
```

###  Alternatives

[spatie/laravel-permission

Permission handling for Laravel 12 and up

12.9k89.8M1.0k](/packages/spatie-laravel-permission)[tymon/jwt-auth

JSON Web Token Authentication for Laravel and Lumen

11.5k49.1M350](/packages/tymon-jwt-auth)[laravel/passport

Laravel Passport provides OAuth2 server support to Laravel.

3.4k85.0M532](/packages/laravel-passport)[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9682.1M97](/packages/roots-acorn)[silber/bouncer

Eloquent roles and abilities.

3.6k4.4M25](/packages/silber-bouncer)[php-open-source-saver/jwt-auth

JSON Web Token Authentication for Laravel and Lumen

8359.8M53](/packages/php-open-source-saver-jwt-auth)

PHPackages © 2026

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