Skip to content
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ and authorization purposes in web applications.
* Stateless authentication using Personal Access Tokens
* Optional Email verification on account registration
* Optional Email-based Two-Factor Authentication after login
* Magic Link Login when a user forgets their password
* Magic Login when a user forgets their password
* Flexible Groups-based access control (think Roles, but more flexible)
* Users can be granted additional Permissions

Expand Down
30 changes: 30 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,34 @@
# Upgrade Guide
## Version 1.2.0 to 1.3.0

### Mandatory Config Changes

A new configuration option has been introduced for controlling how Magic Login codes are generated and delivered.
The following property must be added in **app/Config/Auth.php**.

```php
/**
* --------------------------------------------------------------------
* Magic Login Mode
* --------------------------------------------------------------------
* Determines how magic login works:
*
* - 'clickable' => send an email with a clickable link (default)
* - '<length>-numeric' => send a numeric code with the specified length
* - '<length>-alpha' => send an alphabetic code with the specified length
* - '<length>-alnum' => send an alphanumeric code with the specified length
* - '<length>-oneof' => send a code of the specified length; system chooses
* automatically one of: numeric, alpha, or alnum
*
* Examples:
* 'clickable'
* '6-numeric' // 6-digit numeric code
* '8-alpha' // 8-letter alphabetic code
* '7-alnum' // 7-character alphanumeric code
* '6-oneof' // 6-character code, chosen automatically
*/
public string $magicLoginMode = 'clickable';
```

## Version 1.0.0-beta.8 to 1.0.0

Expand Down
3 changes: 1 addition & 2 deletions docs/customization/extending_controllers.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ various parts of the authentication process:
- **ActionController** handles the after-login and after-registration actions, like Two Factor Authentication and Email Verification.
- **LoginController** handles the login process.
- **RegisterController** handles the registration process. Overriding this class allows you to customize the User Provider, the User Entity, and the validation rules.
- **MagicLinkController** handles the "lost password" process that allows a user to login with a link sent to their email. This allows you to
override the message that is displayed to a user to describe what is happening, if you'd like to provide more information than simply swapping out the view used.
- **MagicLinkController** handles the password-recovery and password-less login flow. It can deliver authentication credentials as a one-time login link or a one-time code (OTP) via email, depending on configuration. Developers may extend this controller to customize user-facing messages, providing clear context about the selected magic login method instead of only swapping view templates.

## How to Extend

Expand Down
1 change: 1 addition & 0 deletions docs/customization/route_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ The above code registers the following routes:
| POST | {locale}/login/magic-link | » | \CodeIgniter\Shield\Controllers\MagicLinkController::loginAction | | toolbar |
| POST | {locale}/auth/a/handle | auth-action-handle | \CodeIgniter\Shield\Controllers\ActionController::handle | | toolbar |
| POST | {locale}/auth/a/verify | auth-action-verify | \CodeIgniter\Shield\Controllers\ActionController::verify | | toolbar |
| POST | {locale}/login/verify-magic-link | verify-magic-link | \CodeIgniter\Shield\Controllers\MagicLinkController::verify | | toolbar |
+--------+----------------------------------+--------------------+--------------------------------------------------------------------+----------------+---------------+
```

Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ The primary goals for Shield are:
- **Stateless Authentication** using **Access Token**, **HMAC SHA256 Token**, or **JWT**
- Optional **Email verification** on account registration
- Optional **Email-based Two-Factor Authentication** after login
- **Magic Link Login** when a user forgets their password
- **Magic Login** when a user forgets their password
- Flexible **Group-based Access Control** (think Roles, but more flexible), and users can be granted additional **Permissions**
- A simple **Auth Helper** that provides access to the most common auth actions
- Save initial settings in your code, so it can be in version control, but can also be updated in the database, thanks to our [Settings](https://github.com/codeigniter4/settings) library
Expand Down
2 changes: 1 addition & 1 deletion docs/references/authentication/session.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ The following is a list of Events and Logging for Session Authenticator.
- Send remember-me cookie w/o session cookie
- OK → no event
- NG → no event
- Magic-link
- Magic-login
1. Post email
- OK → no event
- NG → no event
Expand Down
4 changes: 2 additions & 2 deletions docs/references/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Events::on('failedLogin', function($credentials) {
// Outputs: ['email' => 'foo@example.com'];
```

When the magic link login fails, the following array will be provided:
When the magic login fails, the following array will be provided:

```php
['magicLinkToken' => 'the token value used']
Expand All @@ -59,7 +59,7 @@ Fired immediately after a successful logout. The only argument is the `User` ent

#### magicLogin

Fired when a user has been successfully logged in via a magic link. This event does not have any parameters passed in. The authenticated user can be discovered through the `auth()` helper.
Fired when a user has been successfully logged in via a magic login. This event does not have any parameters passed in. The authenticated user can be discovered through the `auth()` helper.

```php
Events::on('magicLogin', function() {
Expand Down
56 changes: 45 additions & 11 deletions docs/references/magic_link_login.md
Original file line number Diff line number Diff line change
@@ -1,43 +1,77 @@
# Magic Link Login
# Magic Login

Magic Link Login is a feature that allows users to log in if they forget their
password.
Magic Login is an authentication feature that allows users to sign in using a one-time login link(magic link) or a one-time verification code(opt-code) sent to their email. This method provides a secure, password-less entry option, and is especially valuable for users who have forgotten their password, offering a fast and friction-free recovery experience.

## Configuration

### Configure Magic Link Login Functionality
### Configure Magic Login Functionality

Magic Link Login functionality is enabled by default.
Magic Login functionality is enabled by default.
You can change it within the **app/Config/Auth.php** file.

```php
public bool $allowMagicLinkLogins = true;
```

### Magic Link Lifetime
### Magic Login Mode

By default, Magic Link can be used for 1 hour. This can be easily modified
Defines the format and type of the one-time credential sent to users for magic login. The system supports both password-less login via email link as well as multiple one-time verification code formats. The delivery method and code type are specified using configurable mode patterns.

#### Supported Modes

`clickable` (default) — Sends a secure, one-time login link to the user’s email that can be clicked to authenticate instantly.

`<length>-numeric` — Sends a numeric one-time code with the defined character length.

`<length>-alpha` — Sends an alphabet-only one-time code with the defined character length.

`<length>-alnum` — Sends an alphanumeric one-time code with the defined character length.

`<length>-oneof` — Sends a one-time code with the defined length, while the system automatically selects the random code type from: numeric, alpha, or alnum.

```php
public string $magicLoginMode = '8-oneof';
```

### Magic Lifetime

By default, Magic can be used for 1 hour. This can be easily modified
in the **app/Config/Auth.php** file.

```php
public int $magicLinkLifetime = HOUR;
```

!!! warning

One-time numeric codes (such as a 6-digit `6-numeric` format) are inherently more predictable and may be easier to guess. If you plan to deliver a one-time password (OTP) instead of a magic link, we strongly recommend using the `6-oneof` credential mode, which allows the system to randomly generate numeric, alphabetic, or alphanumeric codes. This approach significantly increases unpredictability and makes OTP guessing more difficult.

Additionally, to further mitigate brute-force and code-guessing risks, it is advisable to reduce the `$magicLinkLifetime` to a short window of only a few minutes (e.g., 120–300 seconds).


### Bot Detection

Some apps or devices may try to be "too helpful" by automatically visiting links - for example, to check if they're safe or to prepare for read-aloud features. Since this is a one-time magic link, such automated visits could invalidate it. To prevent this, Shield relies on the framework's `UserAgents::robots` config property (**app/Config/UserAgents.php**) to filter out requests that are likely initiated by non-human agents.
Some apps or devices may try to be "too helpful" by automatically visiting links - for example, to check if they're safe or to prepare for read-aloud features. Since this is a one-time magic link(`clickable`), such automated visits could invalidate it. To prevent this, Shield relies on the framework's `UserAgents::robots` config property (**app/Config/UserAgents.php**) to filter out requests that are likely initiated by non-human agents.

## Responding to Magic Link Logins
Unlike one-time login links, one-time codes (OTP) are not impacted by automated URL visits, since they require manual user input to complete authentication. Therefore, if your application delivers OTP(`6-numeric`,`6-alpha`,`6-alnum`,`6-oneof`) credentials, bot auto-visits do not introduce the same credential-invalidation risk.

## Responding to Magic Logins

!!! note

You need to configure **app/Config/Email.php** to allow Shield to send emails. See [Installation](../getting_started/install.md#initial-setup).

Magic Link logins allow a user that has forgotten their password to have an email sent with a unique, one-time login link. Once they've logged in you can decide how to respond. In some cases, you might want to redirect them to a special page where they must choose a new password. In other cases, you might simply want to display a one-time message prompting them to go to their account page and choose a new password.
Magic Login allows users who have forgotten their password to authenticate using a secure, one-time credential delivered via email. Depending on configuration or product requirements, this credential can be either:

1. unique, single-use login link

2. one-time opt-code for manual entry in the application

After authentication succeeds, the system behavior is up to the developer. Possible responses include redirecting the user to a dedicated password reset screen, or displaying a one-time confirmation message encouraging them to update their password from their account settings.

### Session Notification

You can detect if a user has finished the magic link login by checking for a session value, `magicLogin`. If they have recently completed the flow, it will exist and have a value of `true`.
You can detect if a user has finished the magic login by checking for a session value, `magicLogin`. If they have recently completed the flow, it will exist and have a value of `true`.

```php
if (session('magicLogin')) {
Expand Down
2 changes: 1 addition & 1 deletion phpstan-baseline.php
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@
'rawMessage' => 'Call to deprecated function random_string():
The type \'basic\', \'md5\', and \'sha1\' are deprecated. They are not cryptographically secure.',
'identifier' => 'function.deprecated',
'count' => 1,
'count' => 3,
'path' => __DIR__ . '/src/Controllers/MagicLinkController.php',
];
$ignoreErrors[] = [
Expand Down
43 changes: 34 additions & 9 deletions src/Config/Auth.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ class Auth extends BaseConfig
'action_email_activate_email' => '\CodeIgniter\Shield\Views\Email\email_activate_email',
'magic-link-login' => '\CodeIgniter\Shield\Views\magic_link_form',
'magic-link-message' => '\CodeIgniter\Shield\Views\magic_link_message',
'magic-link-code' => '\CodeIgniter\Shield\Views\magic_link_code', // (new)
'magic-link-email' => '\CodeIgniter\Shield\Views\Email\magic_link_email',
'magic-link-email-code' => '\CodeIgniter\Shield\Views\Email\magic_link_email_code', // (new)
];

/**
Expand Down Expand Up @@ -173,22 +175,45 @@ class Auth extends BaseConfig

/**
* --------------------------------------------------------------------
* Allow Magic Link Logins
* Allow Magic Login
* --------------------------------------------------------------------
* If true, will allow the use of "magic links" sent via the email
* as a way to log a user in without the need for a password.
* By default, this is used in place of a password reset flow, but
* could be modified as the only method of login once an account
* has been set up.
* If true, users may log in using a secure, one-time credential sent by email.
* 5 delivery modes are supported: clickable login **link**, or a one-time **code** for manual entry.
*/
public bool $allowMagicLinkLogins = true;

/**
* --------------------------------------------------------------------
* Magic Link Lifetime
* Magic Login Mode
* --------------------------------------------------------------------
* Specifies the amount of time, in seconds, that a magic link is valid.
* You can use Time Constants or any desired number.
* Determines how magic login works:
*
* - 'clickable' => send an email with a clickable link (default)
* - '<length>-numeric' => send a numeric code with specified length
* - '<length>-alpha' => send an alphabetic code with specified length
* - '<length>-alnum' => send an alphanumeric code with specified length
* - '<length>-oneof' => send a code of specified length; system chooses
* automatically one of: numeric, alpha, or alnum
*
* Examples:
* 'clickable'
* '6-numeric' // 6-digit numeric code
* '8-alpha' // 8-letter alphabetic code
* '7-alnum' // 7-character alphanumeric code
* '6-oneof' // 6-character code, type chosen automatically
*/
public string $magicLoginMode = 'clickable';

/**
* --------------------------------------------------------------------
* Magic Login Lifetime
* --------------------------------------------------------------------
* Time in seconds that a magic login credential remains valid.
* Applies to both **clickable login links** and **one-time codes**.
*
* When using one-time code mode, it is strongly recommended to set
* the lifetime to only a few minutes (e.g., 120-300 seconds) to reduce
* the risk of guessing or brute-force attempts.
*/
public int $magicLinkLifetime = HOUR;

Expand Down
6 changes: 6 additions & 0 deletions src/Config/AuthRoutes.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ class AuthRoutes extends BaseConfig
'MagicLinkController::verify',
'verify-magic-link', // Route name
],
[
'post',
'login/verify-magic-link',
'MagicLinkController::verify',
'verify-magic-link', // Route name
],
],
'logout' => [
[
Expand Down
Loading
Loading