The current pwg_login() workflow always calls log_user() immediately after a successful username/password verification.
This makes it impossible for authentication-related plugins (2FA, rate limiter, IP allow/deny lists, geolocation checks, etc.) to properly intercept or control the login flow.
Today, a plugin must let log_user() happen, then manually block the session and redirect the user (e.g. to a 2FA verification page).
This approach is fragile, creates edge cases, and is not ideal from a security perspective because the session is technically "already logged in" before the plugin can perform its checks.
Proposal:
Introduce a small extension point inside pwg_login(), for example a finalize_login hook, that allows plugins to:
decide whether the login can proceed
block the login temporarily (e.g. while waiting for a second factor)
optionally perform their own log_user() once their checks are completed
The core still handles password verification normally.
Plugins simply get the ability to approve or deny the final authentication step.
The current `pwg_login()` workflow always calls `log_user()` immediately after a successful username/password verification.
This makes it impossible for authentication-related plugins (2FA, rate limiter, IP allow/deny lists, geolocation checks, etc.) to properly intercept or control the login flow.
Today, a plugin must let `log_user()` happen, then manually block the session and redirect the user (e.g. to a 2FA verification page).
This approach is fragile, creates edge cases, and is not ideal from a security perspective because the session is technically "already logged in" before the plugin can perform its checks.
Proposal:
Introduce a small extension point inside `pwg_login()`, for example a finalize_login hook, that allows plugins to:
- decide whether the login can proceed
- block the login temporarily (e.g. while waiting for a second factor)
- optionally perform their own `log_user()` once their checks are completed
The core still handles password verification normally.
Plugins simply get the ability to approve or deny the final authentication step.
<!-- github-sync-id:3621116655 -->
In addition, the current login workflow makes user enumeration possible through timing differences:
when the username exists, password_verify is executed → slower
when the username does not exist, the function returns much faster
A simple mitigation is to introduce a fake user with a valid hashed password and always run the password verification step, regardless of whether the user exists.
This keeps authentication time consistent and prevents timing-based user enumeration attempts.
In addition, the current login workflow makes user enumeration possible through timing differences:
- when the username exists, `password_verify` is executed → slower
- when the username does not exist, the function returns much faster
A simple mitigation is to introduce a _**fake user**_ with a valid hashed password and always run the password verification step, regardless of whether the user exists.
This keeps authentication time consistent and prevents timing-based user enumeration attempts.
<!-- github-comment-id:3527702966 -->
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
The current
pwg_login()workflow always callslog_user()immediately after a successful username/password verification.This makes it impossible for authentication-related plugins (2FA, rate limiter, IP allow/deny lists, geolocation checks, etc.) to properly intercept or control the login flow.
Today, a plugin must let
log_user()happen, then manually block the session and redirect the user (e.g. to a 2FA verification page).This approach is fragile, creates edge cases, and is not ideal from a security perspective because the session is technically "already logged in" before the plugin can perform its checks.
Proposal:
Introduce a small extension point inside
pwg_login(), for example a finalize_login hook, that allows plugins to:log_user()once their checks are completedThe core still handles password verification normally.
Plugins simply get the ability to approve or deny the final authentication step.
In addition, the current login workflow makes user enumeration possible through timing differences:
password_verifyis executed → slowerA simple mitigation is to introduce a fake user with a valid hashed password and always run the password verification step, regardless of whether the user exists.
This keeps authentication time consistent and prevents timing-based user enumeration attempts.