# Authentication levels

## Purpose

The application distinguishes the user's role from the way the current session
was authenticated. `SEC_LEVEL` controls the role required by a route, while
`SEC_HARDLOGIN` distinguishes a full authenticated session from a future
limited soft-login session.

Every route protected by `Security::RequiredLevel()` requires a hard login by
default. A route may accept a soft login only through the explicit
`allow_soft_login: true` argument. No current controller route opts into soft
login.

## Hard login

A hard login is a full application login initiated by entering the account
password. The following session paths retain hard-login status:

- a successful username-and-password login;
- session restoration through a valid `remember_me` token, because "remember
  me" continues the full login previously established with the password;
- administrator impersonation started from a hard administrator session. The
  impersonated identity remains marked separately in the session and inherits
  the administrator session's hard-login status.

The raw remember token is stored only in the secure, HTTP-only cookie. Its hash
is stored in the database, and the token is rotated when it restores a session.
Remember-token restoration must not downgrade the session to soft login.

## Soft login

A soft login is a planned limited session created from a purpose-specific link
without entering the account password. It is intended for narrowly scoped
operations such as reporting an absence from a link sent to the registered
email address. User-facing soft-login links are not active yet.

A future soft-login link must be short-lived, bound to the intended user,
resource, and operation, and preferably single-use. Creating a soft session
must set `logged_in_user_hardlogin` to `false`. That session must not open
ordinary account, instructor, administration, payment, or reporting pages.

`allow_soft_login` is an exception, not a description of the current session.
It must be added only to the exact route intentionally exposed to the scoped
soft-login flow:

```php
Security::RequiredLevel(
    required_level: Security::LEVEL_USER,
    allow_soft_login: true
);
```

Omitting the argument is the normal and secure form:

```php
Security::RequiredLevel(required_level: Security::LEVEL_USER);
```

## Authorization rule

`Security::RequiredLevel()` and `Security::isRequiredLevel()` grant access only
when both conditions are satisfied:

1. the session role is at least the required role;
2. the session is a hard login, unless that exact check explicitly sets
   `allow_soft_login: true`.

The same flag is stored with a validated intended path. This prevents a soft
session from being redirected after authentication to a route that did not opt
into soft login. A normal login-intent token only preserves a local return path;
it does not create a soft login and does not weaken the destination's rule.

## Data and deployment impact

This rule does not change the database schema or historical application data.
No migration, server file operation, or environment configuration change is
required.

## Verification

Run:

```bash
composer test:authentication-security
```

The test verifies that a hard session with the required role is allowed, a soft
session is denied by default, an explicit soft-login exception still enforces
the required role, and intended paths preserve the same rule.

Before enabling the first soft-login link, verify additionally that its token
is scoped and expires as designed, that reuse is rejected when applicable, and
that the resulting session cannot open any route without an explicit
`allow_soft_login: true` opt-in.
