LoginDeviceCookie
in package
The "known device" proof for the password-login throttle: a long-lived signed cookie that says "this browser has signed in to account X before".
Why: the per-account counter across all IPs (ratelimit.loginAccount) let anyone who knows a
user name lock the owner out of password login. When that counter is over its limit, a request
carrying a valid device cookie of the same account still gets its password verified; one
without it is refused unverified (AuthPresenter::internalActionLogin()).
Stateless — no table: v1.<deviceId>.<userId>.<expiresTs>.<hmac>. The HMAC key is derived from
the shared app secret (%jwt.secret%) with a purpose label, so the cookie signature can never be
confused with a JWT or another HMAC of the same secret (AccountDeletionTicket,
EditorAutosaveScope). The device id is random (128 bits) and has its own failure counter
(ratelimit.loginDevice), so a stolen cookie is not a licence for unlimited guessing.
Front and Admin issue the cookie after a successful password or OAuth sign-in and pass its raw value into the Api login call, where it is verified here — the Api trusts the signature, never a flag from the caller.
Table of Contents
Constants
- COOKIE_NAME : string = 'cms_login_device'
- KEY_PURPOSE : string = 'cms:login-device-cookie:v1'
- VERSION : string = 'v1'
Properties
- $key : string
- $rateLimitConfig : RateLimitConfig
Methods
- __construct() : mixed
- getLifetimeSeconds() : int
- issue() : string
- New cookie value for `$userId`. A still valid cookie of the same account keeps its device id (the expiry moves on), so the device failure counter follows the browser, not the sign-in.
- parse() : array{deviceId: string, userId: int}|null
- Signature and expiry only — the account binding is checked by the caller.
- verify() : string|null
- Device id of a valid cookie issued for `$userId`, or null (malformed, forged, expired, another account).
- sign() : string
Constants
COOKIE_NAME
public
string
COOKIE_NAME
= 'cms_login_device'
KEY_PURPOSE
private
string
KEY_PURPOSE
= 'cms:login-device-cookie:v1'
VERSION
private
string
VERSION
= 'v1'
Properties
$key read-only
private
string
$key
$rateLimitConfig read-only
private
RateLimitConfig
$rateLimitConfig
Methods
__construct()
public
__construct(string $secret, RateLimitConfig $rateLimitConfig) : mixed
Parameters
- $secret : string
- $rateLimitConfig : RateLimitConfig
getLifetimeSeconds()
public
getLifetimeSeconds() : int
Return values
intissue()
New cookie value for `$userId`. A still valid cookie of the same account keeps its device id (the expiry moves on), so the device failure counter follows the browser, not the sign-in.
public
issue(int $userId, string|null $currentToken, int $now) : string
Parameters
- $userId : int
- $currentToken : string|null
- $now : int
Return values
stringparse()
Signature and expiry only — the account binding is checked by the caller.
public
parse(string $token, int $now) : array{deviceId: string, userId: int}|null
Parameters
- $token : string
- $now : int
Return values
array{deviceId: string, userId: int}|nullverify()
Device id of a valid cookie issued for `$userId`, or null (malformed, forged, expired, another account).
public
verify(string $token, int $userId, int $now) : string|null
Parameters
- $token : string
- $userId : int
- $now : int
Return values
string|nullsign()
private
sign(string $deviceId, int $userId, int $expires) : string
Parameters
- $deviceId : string
- $userId : int
- $expires : int