laranail/authkit is not published to Packagist, so there is no registry-version badge to show: see Install.
Headless authentication for Laravel 13+. Ships the REST API; no views and no web routes.
Warning
This package is still in development. Breaking changes are imminent; use it in production at your own risk.
- Fortify-backed — password reset, profile updates, password updates, email verification, passkeys, login throttling
- Extensible API tokens — Sanctum is the default
IssueTokenForUserbackend; optional packages can register additional token issuers - Composable — separate actions for credential check vs session login
PHP 8.4+ / Laravel 13.x
laranail/* packages resolve through git, not Packagist. Add the repositories block to your
application's composer.json — Composer ignores a dependency's own repositories, so it must list
the whole transitive closure:
"repositories": [
{ "type": "vcs", "url": "https://github.com/laranail/authkit.git" },
{ "type": "vcs", "url": "https://github.com/laranail/console.git" },
{ "type": "vcs", "url": "https://github.com/laranail/enumerator.git" },
{ "type": "vcs", "url": "https://github.com/laranail/package-tools.git" },
{ "type": "vcs", "url": "https://github.com/laranail/captcha.git" },
{ "type": "vcs", "url": "https://github.com/laranail/db-tools.git" }
]Then:
composer require laranail/authkit
php artisan vendor:publish --tag=laranail::authkit-configSee installation for the full walkthrough.
-
The guard defaults to
web. To use another one, set it in.env:AUTHKIT_GUARD=web
-
Publish and run only the migrations for the features you enable:
php artisan vendor:publish --tag=laranail::authkit-passkey-migrations php artisan vendor:publish --tag=laranail::authkit-two-factor-migrations php artisan migrate
use Illuminate\Http\Request;
use Simtabi\Laranail\AuthKit\Contracts\AttemptEmailPasswordLoginInterface;
use Simtabi\Laranail\AuthKit\Contracts\LoginUserInterface;
use Simtabi\Laranail\AuthKit\Enums\AuthStatus;
public function store(Request $request, AttemptEmailPasswordLoginInterface $attempt, LoginUserInterface $login)
{
$result = $attempt->execute(request: $request, guard: 'web');
return match ($result->status) {
AuthStatus::Passed => tap(redirect()->intended('/dashboard'), fn () => $login->execute($result->user, 'web')),
AuthStatus::Failed => back()->withErrors(['email' => 'Invalid credentials.']),
AuthStatus::Throttled => abort(429),
};
}Issue an API token instead of a session:
use Simtabi\Laranail\AuthKit\Contracts\IssueTokenForUserInterface;
$token = app(IssueTokenForUserInterface::class)->execute($user, name: 'mobile');
// $token->token — the plain-text token, shown onceThe full walkthrough is in Getting started; everything else is in the documentation index.
Full documentation: https://opensource.simtabi.com/documentation/laranail/authkit/
- Installation — requirements, the repositories block, publishing config
- Getting started — authenticate a user with the core alone
- Configuration — guard, rate limits, and Fortify features
- Architecture — layering, what is delegated to Fortify, the extension seams
- Security — the guarantees this package makes and the ones it does not
- Release — versioning, tagging, and ordering across the family
- Login · Registration · Logout
- Password reset · Password updates
- Profile management · Email verification
- Browser sessions · API routes · API tokens
- Two-factor authentication
- Social login · Passkeys · API tokens
- Testing — how the suite is arranged and what it does not cover
- Changelog · Contributing · Security policy
.env:
AUTHKIT_GUARD=web
AUTHKIT_RATE_LIMIT_MAX_ATTEMPTS=5
AUTHKIT_RATE_LIMIT_DECAY_MINUTES=1config/laranail/authkit.php:
return [
'guard' => env('AUTHKIT_GUARD', 'web'),
'rate_limit' => [
'max_attempts' => (int) env('AUTHKIT_RATE_LIMIT_MAX_ATTEMPTS', 5),
'decay_minutes' => (int) env('AUTHKIT_RATE_LIMIT_DECAY_MINUTES', 1),
],
'fortify' => [
'views' => false,
'features' => ['reset-passwords', 'update-profile-information', 'update-passwords', 'email-verification', 'passkeys'],
],
];Remove passkeys from laranail.authkit.fortify.features to disable Fortify's passkey routes. Auth Kit only enables and configures Fortify; passkey ceremonies, responses, and persistence remain provided by Fortify and laravel/passkeys.
- TOTP two-factor authentication is opt-in. See the TOTP setup and API flow.
- Before production, configure HTTPS, secure session cookies, a working mail transport, and Turnstile keys when bot protection is enabled.
Passkey support uses Fortify's native integration with laravel/passkeys. It is stateful and requires the consuming application's authenticatable model to implement Fortify's PasskeyUser contract and use Auth Kit's morph-aware PasskeyAuthenticatable trait:
use Laravel\Fortify\Contracts\PasskeyUser;
use Simtabi\Laranail\AuthKit\PasskeyAuthenticatable;
class User extends Authenticatable implements PasskeyUser
{
use PasskeyAuthenticatable;
}The published migration stores ownership in passkeyable_type and passkeyable_id, so the same passkey implementation can be used by users, admins, or another authenticatable model. Auth Kit configures its morph-aware Passkey model for Fortify and retains the vendor package's WebAuthn actions, controllers, responses, and credential validation.
Publish Auth Kit's passkeys migration in the consuming application and run it:
php artisan vendor:publish --tag=laranail::authkit-passkey-migrations
php artisan migrateDo not publish Fortify's migration tag for passkeys; Auth Kit owns this table migration while Fortify and laravel/passkeys provide the model and WebAuthn behavior.
Configure the relying party and allowed browser origins in config/fortify.php. The defaults use the host and URL from APP_URL; set them explicitly for production HTTPS domains when necessary:
'passkeys' => [
'relying_party_id' => parse_url(config('app.url'), PHP_URL_HOST),
'allowed_origins' => [config('app.url')],
'user_handle_secret' => env('PASSKEYS_USER_HANDLE_SECRET', config('app.key')),
'timeout' => 60000,
],Fortify's passkey management routes honor the fortify-options.confirmPassword setting and the passkeys rate limiter in fortify.limiters. Passkey login and confirmation require the configured stateful guard and session, so Auth Kit does not expose equivalent Sanctum API endpoints.
The browser ceremony is application-owned. Install the official client and connect it to Fortify's canonical route names; Auth Kit does not bundle JavaScript or a build pipeline:
npm install @laravel/passkeysThe application client should use Fortify's /passkeys/login/options, /passkeys/login, /passkeys/confirm/options, /passkeys/confirm, /user/passkeys/options, /user/passkeys, and /user/passkeys/{passkey} endpoints. Keep the browser origin, relying-party ID, and APP_URL aligned or WebAuthn validation will fail.
| Action | Purpose |
|---|---|
AttemptEmailPasswordLogin |
Verify email + password against a guard, returns AuthResult |
LoginUser |
Log user into session + regenerate session |
LogoutUser |
Log out + invalidate session |
CreateNewUser |
Validate and create user (Fortify CreatesNewUsers) |
ResetUserPassword |
Validate and reset password (Fortify ResetsUserPasswords) |
UpdateUserProfileInformation |
Validate and update profile (Fortify UpdatesUserProfileInformation) |
UpdateUserPassword |
Validate and update password (Fortify UpdatesUserPasswords) |
IssueTokenForUser |
Issue a token using the configured backend, returns TokenResult |
CheckEmailExists |
Check if email is registered |
FindUserByEmail |
Retrieve user by email |
AuthResult — returned by login actions:
AuthResult::passed($user) // credentials valid
AuthResult::failed() // credentials invalid
AuthResult::throttled($seconds) // rate limitedCheck with $result->isPassed() or match on $result->status (AuthStatus::Passed|Failed|Throttled).
TokenResult — returned by IssueTokenForUser:
new TokenResult(user: $user, token: $token)Extend these to wire up your own routes. JSON responses are handled automatically.
| Controller | Overridable methods |
|---|---|
AbstractAttemptEmailPasswordLoginController |
passed(), failed(), throttled() |
AbstractCheckEmailExistsController |
respond() |
AbstractLogoutController |
loggedOut() |
AbstractRegisterController |
registered() |
Social login is maintained in laranail/authkit-social-login,
which extends this package. See its social login guide
for installation, providers, routes, persistence, and account-linking behavior.
$result = app(AttemptEmailPasswordLogin::class)->execute(
request: $request,
guard: 'web',
);
if (! $result->isPassed()) {
return back()->withErrors(['email' => 'Invalid credentials']);
}
app(LoginUser::class)->execute($result->user, guard: 'web');
return redirect()->intended('/dashboard');$tokenResult = app(IssueTokenForUser::class)->execute(
user: $user,
name: 'api-token',
);
return response()->json([
'token' => $tokenResult->token,
'user' => $tokenResult->user,
]);| Package | Role |
|---|---|
laranail/authkit |
Headless core — actions, contracts, result objects, REST API |
laranail/authkit-preset |
Blade scaffolding on top of the core |
laranail/authkit-social-login |
Social login through Socialite |
laranail/authkit-sso |
SAML 2.0 and OIDC single sign-on |
laranail/authkit-oauth |
OAuth server, apps and scopes |
laranail/authkit-tenancy |
Multi-tenancy |
laranail/authkit-ldap |
LDAP and Active Directory |
The family shares one root namespace, Simtabi\Laranail\AuthKit\, with each sibling a segment
under it.
See CONTRIBUTING.md. Report security issues privately — SECURITY.md.
MIT