Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Security Laravel Package

nette/security

Nette Security is a compact PHP library for authentication and authorization: secure password hashing (bcrypt/argon2), user identities and login, role and permission checks, and CSRF protection. It works standalone or as part of the Nette Framework.

View on GitHub
Deep Wiki
Context7

Getting Started

nette/security is not a Laravel package. There is no service provider, no guard, no config file that Laravel auto-loads. What you get is a small, strict, framework-agnostic set of classes: User, Authenticator, UserStorage, Identity, Authorizator, Permission, Role, Passwords. Your first job on day one is deciding whether you are adopting it as the auth stack or as the policy/roles layer only.

composer require nette/security   # v3.2.x requires PHP 8.1+

Where to look first (read in this order, ~20 minutes total):

  1. src/Security/User.php — the façade you will inject everywhere. Every method you need is here: login(), logout(), isLoggedIn(), getIdentity(), isInRole(), and the authorizator passthrough.
  2. src/Security/Authenticator.php and src/Security/IdentityHandler.php — the two login contracts.
  3. src/Security/UserStorage.php — the three-method interface you will almost certainly reimplement over Laravel's session.
  4. src/Security/Authorizator.php + src/Security/Authorization/Permission.php — the ACL.
  5. readme.md — short, accurate, and the only real docs. The GitHub releases notes are the changelog of record.

First use case — bind User into the container and use it read-only. Don't rip out Laravel's login yet. Register the ACL and start answering "may this user do X?" with it:

// app/Providers/SecurityServiceProvider.php
public function register(): void
{
    $this->app->singleton(\Nette\Security\User::class, function ($app) {
        $user = new \Nette\Security\User(
            $app->make(App\Security\LaravelUserStorage::class),
            $app->make(App\Security\EloquentAuthenticator::class),
            $app->make(\Nette\Security\Authorizator::class),
        );
        return $user;
    });

    $this->app->singleton(\Nette\Security\Authorizator::class, function () {
        $acl = new \Nette\Security\Authorization\Permission();
        $acl->allow('admin', \Nette\Security\Authorizator::All, \Nette\Security\Authorizator::All);
        $acl->allow('editor', 'article', ['create', 'edit']);
        $acl->allow('member', 'article', 'view');
        return $acl;
    });
}

Now app(User::class)->isAllowed('editor', 'article', 'edit') works from anywhere, including queued jobs and console commands, without touching the HTTP session.

Implementation Patterns

Pattern A — Keep Laravel's login, use nette/security as the policy engine (recommended)

Laravel handles the session, remember-me, password resets, and throttling. nette/security owns roles, resources, privileges, and inheritance. Bridge in one direction only:

// A thin resolver that turns the current Laravel user into a nette Role
final class CurrentRole
{
    public function __construct(private \Nette\Security\Authorizator $acl) {}

    public function can(string $resource, string $privilege): bool
    {
        $role = auth()->user()?->role ?? 'guest';   // your column
        return $this->acl->isAllowed($role, $resource, $privilege);
    }
}

Then expose it through Laravel's own Gate so Blade, policies, and @can keep working unchanged:

Gate::define('article.edit', fn ($laravelUser) => app(CurrentRole::class)->can('article', 'edit'));

This is the lowest-risk path: no session duplication, no guard replacement, and you still get role inheritance, wildcards, and per-resource privilege lists.

Pattern B — Full swap: custom UserStorage over Laravel's session

If you want \Nette\Security\User to be the single source of truth, implement the storage interface against Laravel's session repository rather than Nette's SessionStorage (which would start a second, incompatible PHP session).

namespace App\Security;

use Nette\Security\{IIdentity, UserStorage};

final class LaravelUserStorage implements UserStorage
{
    private const Key = 'nette_security';

    public function __construct(private \Illuminate\Contracts\Session\Session $session) {}

    public function storeAuthentication(bool $state): void
    {
        $data = $this->session->get(self::Key, []);
        $data['authenticated'] = $state;
        $this->session->put(self::Key, $data);
    }

    public function getState(): array
    {
        $d = $this->session->get(self::Key, []);
        // [bool $authenticated, ?IIdentity $identity, ?string $logoutReason]
        return [$d['authenticated'] ?? false, $d['identity'] ?? null, $d['reason'] ?? null];
    }

    public function setExpiration(?string $time, bool $clearIdentity = false): void
    {
        // store an absolute timestamp and check it inside getState()
    }
}

Check the exact method signatures of Nette\Security\UserStorage in your installed version — the interface has moved between 3.1 and 3.2 and the return tuple order matters.

Pattern C — IdentityHandler and guest identities (v3.2.4+)

If your authenticator implements getGuestIdentity(): ?IIdentity, anonymous visitors stop being a bare $guestRole string and become a real identity with its own data and roles. This is genuinely useful for anonymous-cart, tenant-scoped, or "preview as visitor" flows:

final class EloquentAuthenticator implements \Nette\Security\IdentityHandler
{
    public function authenticate(string $username, string $password): IIdentity { /* ... */ }

    public function sleepIdentity(IIdentity $identity): IIdentity { return $identity; }
    public function wakeupIdentity(IIdentity $identity): ?IIdentity { /* refresh roles from DB */ }

    public function getGuestIdentity(): ?IIdentity
    {
        return new \Nette\Security\SimpleIdentity('guest', ['visitor'], ['tenant' => tenant()->id]);
    }
}

Note: the guest identity is resolved on read and is never written to storage.

Pattern D — ACL modelling

Roles inherit by passing parents: new Role('admin', 'editor'). Use Authorizator::All ('*') for wildcards, null role for "everyone including guests", and Permission::setAssertion() for the handful of rules that need row-level context:

$acl->allow('member', 'article', 'edit', function (Role $role, Resource $res, string $priv, array $ctx) {
    return $ctx['author_id'] === $ctx['user_id'];
});

Keep assertions to one short callback. If you find yourself writing ten of them, you want Laravel policies instead.

Testing

User is trivially constructible, which makes it excellent in unit tests:

$user = new \Nette\Security\User(new FakeStorage, $authenticator, $acl);
$user->login('alice', 'secret');
Assert::true($user->isAllowed('article', 'edit'));

The ACL itself is pure data — test Permission directly without any HTTP layer.

Gotchas and Tips

  • The identity survives logout by default. This is the single biggest surprise for Laravel devs. After $user->logout(), getIdentity() still returns the identity so you can personalize the post-logout page. getId() also still works. Set User::$persistIdentity = false (also configurable via security.authentication in DI) if you expect Auth::user()-style null. Always branch on isLoggedIn(), never on getIdentity() !== null.
  • Use getLogoutReason(). User::LogoutManual vs LogoutInactivity lets you show "signed out due to inactivity" instead of a generic message. Cheap UX win.
  • The identity is cached per User instance. Role changes made mid-request (or by an admin in another tab) are invisible until you call $user->refreshStorage(). Call it after any write that changes roles.
  • Do not use Nette's SessionStorage inside Laravel. It depends on nette/http's Session, which starts its own PHP session with its own cookie. You will end up with two sessions, one of which silently loses writes. Always implement UserStorage over Illuminate\Session.
  • CookieStorage is similarly Nette-HTTP-bound (uses Nette\Utils\Random + its own HMAC and SameSite handling). If you want remember-me, use Laravel's remember_token, not this.
  • Expiration is sliding, and that was a bug source. v3.2.3/3.2.4 fixed SessionStorage refreshing the timestamp on an already-expired session. If you are on ≤3.2.2 and care about session integrity (audits, SOC 2), upgrade — your own UserStorage implementation must replicate the fix: check expiry before touching the stored timestamp.
  • IDs are strings-or-int with strict numeric detection (v3.2.5 moved to ctype_digit). Don't pass "0012" and expect it to equal 12; don't let MySQL unsigned bigints overflow into floats. Pass (string) $id consistently.
  • Passwords::hash() throws on an empty password (v3.0.5). Validate before hashing. The class defaults to bcrypt; Passwords::needsRehash($hash) is there for algorithm migrations, but if you already use Laravel's Hash, don't mix the two — just use Hash::check() inside your Authenticator.
  • Many classes and members are final, and everything is declare(strict_types=1). Extend by composition and DI, not inheritance. Passing an int where a string is declared will throw, not coerce.
  • No UserPanel for you. The Tracy debug bar panel is Nette-only; ignore it.
  • AuthenticationException is the only thing login() throws — catch it and map to a 422/redirect yourself. There is no throttle, no rate limiting, no CSRF, no session fixation protection built in. Laravel's Auth gives you all of that; nette/security gives you none of it.
  • Extension points worth knowing: IdentityHandler::sleepIdentity() / wakeupIdentity() run around persistence — ideal for shrinking the identity to just an ID in the session and rehydrating roles on each request (this is what avoids stale permissions in the session entirely).
  • Config quirks: the security: DI section (roles, resources, authentication) only exists inside Nette's SecurityExtension. In Laravel you build the Permission object by hand — put it behind a config array so roles aren't hardcoded in PHP.
  • Upgrade path friction: v3.2.0 removed IUserStorage outright and v3.1 removed all I prefixes. Any tutorial or StackOverflow answer older than 2022 uses the wrong class names (IAuthenticator, Identity, IUserStorage). Trust the source, not the snippets.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
mydnic/volet-feature-board
wmde/fundraising-phpcs
friendlylabs/surveyr
mg-techlegend/laravel-notify-africa
islamabdurahman/laravel-sms-service
dmhendricks/b2-sdk-php
aplicativa/laravel-admin
ezavalishin/vkma
jackardios/laravel-json-api-paginate
viral/laravel-adminpanel
dieppk/log-viewer
daimakuai-ext/helpers
codexshaper/php-blade-view
abraxas81/charts
adamlien/laravel-cas
quimgc/tasks
sistem-atc/bank-sdk
core45/laravel-cookieconsent
projectsend/community-modules
ovac/laravel-hubtel-payment