Skip to content
Back to skills

Nette Security

BSecurity

Invoke before implementing or modifying authentication, login/logout or permission checks in Nette. Covers the Authenticator interface, SimpleIdentity, Nette\Security\User, the Passwords hashing service, Authorizator, Permission ACL (roles, resources, privileges, assertions), IdentityHandler, user storage (session/cookie), login expiration, and the `security:` NEON section (users, roles, resources, rules). Also trigger for sign-in presenters and forms, isAllowed()/isInRole() checks, password ...

  • 44 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 6, 2026
ai-agentsgophptestingapidatabasebackendsecuritydocumentation

Works with

  • api

Security analysis

B88/100
  • criticalImpersonates system messages to override safety constraints

Pro shows the line behind each finding and how to fix it

Scanned October 1, 2026

npx -y skills add nette/claude-code --skill nette-security --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Nette Security?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Nette Security
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/nette-nette-security/badge)](https://www.skillsdirectory.com/skills/nette-nette-security)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: nette-security
description: Invoke before implementing or modifying authentication, login/logout or permission checks in Nette. Covers the Authenticator interface, SimpleIdentity, Nette\Security\User, the Passwords hashing service, Authorizator, Permission ACL (roles, resources, privileges, assertions), IdentityHandler, user storage (session/cookie), login expiration, and the `security:` NEON section (users, roles, resources, rules). Also trigger for sign-in presenters and forms, isAllowed()/isInRole() checks, password hashing or rehashing, AuthenticationException error codes, and stale roles in the session. Not for Nette Forms validation rules nor for HTTP session configuration itself.
---

## Nette Security

Installed with `composer require nette/security`. The framework registers `security.user` (`Nette\Security\User`), `security.passwords`, `security.userStorage`, plus `security.authenticator` / `security.authorizator` when the `security:` config section defines them. All are autowired – inject `Nette\Security\User` or `Nette\Security\Passwords` by type; in presenters use `$this->getUser()`.

### Current names vs. Nette 2.x names

Using an old name still "works" (aliases/deprecated stubs exist), which makes the mistake silent. Always write the current one:

| Current (all in `Nette\Security\`) | Deprecated 2.x name |
|---|---|
| `SimpleIdentity` | `Identity` |
| `Authenticator` | `IAuthenticator` – a **different, array-based** `authenticate(array $credentials)` contract; never implement it |
| `Authorizator`, `Role`, `Resource` | `IAuthorizator`, `IRole`, `IResource` |
| `Authenticator::IdentityNotFound`, `InvalidCredential` | `IDENTITY_NOT_FOUND`, `INVALID_CREDENTIAL` |
| `Authorizator::All`, `Allow`, `Deny` | `ALL`, `ALLOW`, `DENY` |
| `User::LogoutManual`, `LogoutInactivity` | `LOGOUT_MANUAL`/`MANUAL`, `LOGOUT_INACTIVITY`/`INACTIVITY` |

### Authenticator

```php
// interface Nette\Security\Authenticator – error codes: IdentityNotFound, InvalidCredential, Failure, NotApproved
function authenticate(string $username, string $password): IIdentity;
```

`InvalidCredential` is **singular**, no trailing `s`. The error code is the exception's second argument:

```php
public function authenticate(string $username, string $password): SimpleIdentity
{
	$row = $this->database->table('users')->where('username', $username)->fetch();
	if (!$row) {
		throw new AuthenticationException('User not found.', self::IdentityNotFound);
	}
	if (!$this->passwords->verify($password, $row->password)) {
		throw new AuthenticationException('Invalid password.', self::InvalidCredential);
	}
	if ($this->passwords->needsRehash($row->password)) { // transparent hash upgrade
		$this->database->table('users')->wherePrimary($row->id)
			->update(['password' => $this->passwords->hash($password)]);
	}

	return new SimpleIdentity($row->id, $row->role, ['name' => $row->username]);
}
```

Register it as an ordinary service (`services: - MyAuthenticator`); `User` picks it up by autowiring. With several authenticators, mark each `autowired: self` and call `$user->setAuthenticator($this->authenticator)` before `login()`. The return type may be narrowed to `SimpleIdentity` (covariance). `SimpleIdentity(string|int $id, string|array|null $roles = null, ?iterable $data = null)` – roles accept a single string. Data is read via magic properties (`$identity->name`) and `getData()`; `IIdentity` declares only `getId()` and `getRoles()`.

### Passwords is a service with instance methods

`hash()`, `verify()` and `needsRehash()` are **instance** methods on the injected service – not static, and not a wrapper to skip in favour of `password_hash()` / `password_verify()`:

```php
$hash = $passwords->hash($password);       // throws on empty password
$passwords->verify($password, $hash);      // bool
$passwords->needsRehash($hash);            // bool – rehash on successful login
```

Configure the algorithm by overriding the service, or with the static factories (3.2.6+):

```neon
services:
	security.passwords: Nette\Security\Passwords(::PASSWORD_BCRYPT, [cost: 12])
	# or Nette\Security\Passwords::bcrypt(12)
	# or Nette\Security\Passwords::argon2id(memoryCost: 131072, timeCost: 4) – throws NotSupportedException without Argon2
```

Store hashes in a `VARCHAR(255)` column – `PASSWORD_DEFAULT` may change length between PHP versions.

### User

```php
$user->login($username, $password);   // or login($identity) directly; throws AuthenticationException
$user->logout();                      // keeps the identity!
$user->logout(true);                  // discards it
$user->getIdentity();                 // ?IIdentity – nullable
$user->getId();                       // string|int|null
$user->getLogoutReason();             // User::LogoutManual | User::LogoutInactivity | null
$user->onLoggedIn[] = fn() => ...;    // event array; also $onLoggedOut
```

**`logout()` does not clear the identity by default.** `getIdentity()` and `getId()` keep returning it afterwards, so "identity exists" is *not* "logged in" – gate on `isLoggedIn()`. Use `logout(true)`, or set `$user->persistIdentity = false` / `security: authentication: persistIdentity: false` to disable retention globally. Cookie storage cannot retain it either way.

`isInRole()` / `getRoles()` / `isAllowed()` work with **effective** roles: the identity's roles when logged in, otherwise `$user->guestRole` (`'guest'`), never the retained identity's roles – so they need no `isLoggedIn()` guard. Session-id regeneration on login and logout is done by `SessionStorage`; do not add your own.

### Expiration

`$user->setExpiration('30 minutes')` must be called **before** `login()`; `null` cancels it. It is inactivity-based and **sliding** – the window restarts on every request that touches the storage. On expiry the user becomes logged out and `getLogoutReason()` returns `User::LogoutInactivity`; the identity survives unless `setExpiration($time, clearIdentity: true)`. It cannot outlive the session itself (`session: expiration:`, default 3 hours).

### Authorization

`Authorizator` has one method, and its parameters are **string-typed**: `isAllowed(?string $role, ?string $resource, ?string $privilege): bool`. `User::isAllowed(mixed $resource = Authorizator::All, mixed $privilege = Authorizator::All)` omits the role – it loops over the user's effective roles and returns true if any is allowed. `Authorizator::All` is `null` and means "anything".

`Permission` is the built-in ACL:

```php
$acl = new Nette\Security\Permission;
$acl->addRole('registered', 'guest');       // roles inherit
$acl->addResource('perex', 'article');      // resources inherit too
$acl->allow('guest', ['article', 'perex'], 'view');
$acl->allow('admin', $acl::All, ['view', 'edit']);
$acl->deny('admin', 'poll', 'edit');
$acl->allow('registered', 'article', 'edit', fn(Permission $acl, ?string $role, ?string $res, ?string $priv)
	=> $acl->getQueriedRole()->id === $acl->getQueriedResource()->authorId);
```

`Permission::isAllowed(string|Role|null, string|Resource|null, ?string)` also takes objects implementing `Role::getRoleId()` / `Resource::getResourceId()`; assertions then read them back with `getQueriedRole()` / `getQueriedResource()`.

**Multiple-parent role weight: the LAST parent listed wins.** `addRole('john', ['admin', 'guest'])` gives guest's `deny` precedence; `['guest', 'admin']` gives admin's `allow` precedence. This is easy to get backwards. Register the finished ACL through a factory: `services: - App\Model\AuthorizatorFactory::create`.

### NEON configuration

```neon
security:
	users:                  # creates SimpleAuthenticator – testing only
		johndoe: 'secret123'
		janedoe:
			password: 'secret123'
			roles: [admin]
			data: {name: Jane}   # ends up in the identity

	roles: {registered: [guest]}      # role: parent(s)
	resources: {comment: [article]}   # resource: parent

	rules:                  # 3.2.6+; [role(s), resource(s), privilege(s)], omitted = all
		allow:
			- [guest, article, view]
			- [registered, comment, [add, edit]]
			- [admin]       # everything
		deny:
			- [banned, comment, add]

	authentication:
		expiration: 30 minutes
		storage: session    # or cookie
		persistIdentity: true
		cookieName: userId  # cookie storage only; plus cookieDomain, cookieSamesite
```

`SimpleAuthenticator` compares usernames **case-insensitively**, and accepts either a plaintext password or a crypt-format hash (auto-detected, 3.2.6+); always quote passwords and hashes – they contain characters NEON and DI treat specially. Defining `roles`/`resources`/`rules` registers a `Permission` service – if you also register your own authorizator, define everything there instead, or you end up with two competing services.

### IdentityHandler – fixing stale roles

The identity is serialized into the storage at login, so **role and data changes do not reach a logged-in user until re-login**. Let the authenticator also implement `Nette\Security\IdentityHandler` to refresh on every request:

```php
public function sleepIdentity(IIdentity $identity): IIdentity { return $identity; } // before writing

public function wakeupIdentity(IIdentity $identity): ?IIdentity  // after reading; null forces a logout
{
	$identity->setRoles($this->facade->getUserRoles($identity->getId()));
	return $identity;
}

// used whenever nobody is logged in; null = no guest identity
public function getGuestIdentity(): ?IIdentity { return new SimpleIdentity('guest', ['guest']); }
```

`sleepIdentity()` is also how cookie storage works: return a proxy identity whose id is a random `authtoken`, and reload the real row from it in `wakeupIdentity()`. The guest identity is never persisted.

### Traps

- **Never put the password hash into the identity.** `new SimpleIdentity($row->id, $row->role, (array) $row)` copies it into the session/cookie – unset it first.
- `getIdentity()` is nullable; `$user->getIdentity()->name` on a guest is a fatal error. Prefer `$user->getId()`, or check `isLoggedIn()`.
- Reading an unknown identity data key raises an undefined-key warning – it is a plain array lookup, not a null-safe getter.
- Multiple independent logins in one session need `$user->getStorage()->setNamespace('backend')` in every place of that section (e.g. `BasePresenter::checkRequirements()`), plus `$user->refreshStorage()` if switched mid-request.

### Online Documentation

For details, see the official documentation:

- [Authentication](https://doc.nette.org/en/security/authentication) – login, identity, storage, IdentityHandler
- [Authorization](https://doc.nette.org/en/security/authorization) – roles, resources, Permission ACL
- [Password Hashing](https://doc.nette.org/en/security/passwords) – Passwords API
- [Security Configuration](https://doc.nette.org/en/security/configuration) – the `security:` section

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…