public inbox for [email protected]  
help / color / mirror / Atom feed
From: Ajit Awekar <[email protected]>
To: Zsolt Parragi <[email protected]>
Cc: Daniel Gustafsson <[email protected]>
Cc: VASUKI M <[email protected]>
Cc: PostgreSQL Hackers <[email protected]>
Subject: Re: [OAuth2] Infrastructure for tracking token expiry time
Date: Mon, 16 Mar 2026 19:27:17 +0530
Message-ID: <CAER375OVXtyp7GiYNes30jn_pOrXoC_qDEEVGS2aDErm4oKiYQ@mail.gmail.com> (raw)
In-Reply-To: <CAER375P8M2b3Nd8r_+ynbhgHgGcybiLRPN64TAN=hQ4YQFO=nQ@mail.gmail.com>
References: <CAER375PhG5an=p1=6QS6vWi=BHxR+ViJmYPDkkEtpgVsfCcu_w@mail.gmail.com>
	<CAE2r8H5QAng_rRrkVmGbLuQSgbMz94tpOOOdJKeuHj=go0nXqg@mail.gmail.com>
	<CAN4CZFOe-0jTR7_s2uciX9TNKxRvd2h8avAw9iFO6VPu0CChsQ@mail.gmail.com>
	<CAER375Oh6U_kqP0SK8OP47vy3PBd4p1C027Gaod3B2bqKgMFoQ@mail.gmail.com>
	<CAN4CZFNV69LS6H87sV-iPO9w_V-_uko4_G_0QKb1cokJvWhF6g@mail.gmail.com>
	<CAE2r8H6Tc6F2BM-JqC+gp-HQKCzfHOx02Xj5MmuS-AY4jfN5iw@mail.gmail.com>
	<CAER375Mtf-7LcR1zNks67k57r3b5yTy9sHxRQ78Y1+xmTVncMw@mail.gmail.com>
	<[email protected]>
	<CAN4CZFPKY2m1bEm51g-1jmBfCMohfe5VFoKuCnbhiGP1FBBNrQ@mail.gmail.com>
	<[email protected]>
	<CAN4CZFMaAvHSBbQ25o=yiWqn=p7jBNUBABZvqoU1x1-7NWPH9Q@mail.gmail.com>
	<CAER375P8M2b3Nd8r_+ynbhgHgGcybiLRPN64TAN=hQ4YQFO=nQ@mail.gmail.com>

Hi,

Please find the attached first version of the patch providing credential
validation framework.

The credential validation framework provides a mechanism to continuously
validate authentication credentials during an active session. This enables
the server to periodically check credential validity and take appropriate
action when credentials expire or become invalid.

  Currently, Postgres validates credentials only at connection time. Once
authenticated, a session remains active even if:

  - A user's rolvaliduntil expiration time passes
  - An OAuth bearer token expires



* Proposed Solution*
  The patch introduces a credential validation framework that:

  1. Periodically checks credential validity during active sessions
  2. Terminates sessions when credentials expire or become invalid

*Implementation*

  The framework consists of:

  - Core infrastructure (auth-validate.c/h): Manages validation callbacks,
dispatches validation checks based on authentication method
  - Method implementations (auth-validate-methods.c/h): Contains validators
for password-based auth (checks rolvaliduntil in pg_authid) and OAuth
  (delegates to validator's expire_cb)

  Validation is triggered during query execution in both simple and
extended query protocol paths, using a time-based approach to limit
overhead.


*  Configuration*
  Two new GUC parameters:

  credential_validation.enabled = false   # enable/disable validation
  credential_validation.interval = 1      # check interval in minutes (1-60)


* Extensibility*
  New authentication methods can be supported by:
  1. Adding an enum value to CredentialValidationType
  2. Implementing a validation callback
  3. Registering it via RegisterCredentialValidator()


 Should there be per-authentication-method enable/disable settings?

Thanks & Best Regards,
Ajit


On Fri, 20 Feb 2026 at 15:12, Ajit Awekar <[email protected]> wrote:

>
> Thanks a lot Daniel, Zslot, Vasuki for your review comments.
>
> >The mechanism used is however a secondary discussion,
> >first thing to get in place is a design for how to handle mid-connection
> >credential expiration.
>
> This patch introduces a generic credential validation framework that
> allows
>  us to periodically validate authentication credentials during active
>  database sessions. When enabled, this feature detects expired
>  credentials and terminates sessions that are no longer valid.
>
>  Added GUCs
> Credential_validation.enabled = on   // Enable or Disable Credential
> validation
> Credential_validation.interval = 120  //Frequency in seconds of running
> credential validation
>
>  The callback mechanism works by:
>   - Defining a CredentialValidationCallback function pointer type
>   - Maintaining an array of validators indexed by authentication method
>   - Allowing other auth mechanisms to register validators via
>     RegisterCredentialValidator()
>   - Selecting the appropriate validator at runtime based on the session's
>     authentication method
>
> The current implementation primarily supports password-based
> authentication methods, verifying that passwords haven't expired. It can be
> extended to any authentication method.
> This patch is WIP. I am submitting it now to get early feedback on the
> overall design and approach.
>
> Thanks & Best Regards,
> Ajit
>
> On Wed, 18 Feb 2026 at 22:29, Zsolt Parragi <[email protected]>
> wrote:
>
>> > but I still think that neither should overload
>> > what FATAL error means
>>
>> I see, I misunderstood what you meant by graceful there. In this case,
>> this is also a good comment for the password expiration thread,
>> currently that also uses FATAL errors for terminating a connection
>> when the password expires.
>>
>> What other option do you see? Something new for this use case like
>> GoAway, and clients not understanding it simply get disconnected after
>> some grace period? Or using the recently merged connectionWarning to
>> send a warning to the client, and disconnect it shortly if it doesn't
>> do anything to fix the situation?
>>
>> When I tested the password expiration patch I noticed that deleted
>> users who still have remaining active connections currently get ERRORs
>> for every statement that requires permission checks, so in this regard
>> using ERROR/FATAL for the situation seemed fine to me - it's similar
>> to what already happens in some edge cases with authentication.
>>
>


Attachments:

  [application/octet-stream] credential_validation_V1.patch (23.2K, 3-credential_validation_V1.patch)
  download | inline diff:
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 98eb2a8242d..32e4c7280e5 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -18,6 +18,8 @@ OBJS = \
 	auth-oauth.o \
 	auth-sasl.o \
 	auth-scram.o \
+	auth-validate-methods.o \
+	auth-validate.o \
 	auth.o \
 	be-fsstubs.o \
 	be-secure-common.o \
diff --git a/src/backend/libpq/auth-oauth.c b/src/backend/libpq/auth-oauth.c
index 11365048951..c6a7840cb7a 100644
--- a/src/backend/libpq/auth-oauth.c
+++ b/src/backend/libpq/auth-oauth.c
@@ -892,3 +892,18 @@ done:
 
 	return (*err_msg == NULL);
 }
+
+/*
+ * Check if an OAuth token has expired.
+ * This is called from credential validation to check token validity.
+ */
+bool
+CheckOAuthValidatorExpiration(void)
+{
+	/* Delegate to validator's expire_cb if available */
+	if (ValidatorCallbacks != NULL && ValidatorCallbacks->expire_cb != NULL)
+		return ValidatorCallbacks->expire_cb(validator_module_state);
+
+	/* No expire_cb, assume valid */
+	return true;
+}
diff --git a/src/backend/libpq/auth-validate-methods.c b/src/backend/libpq/auth-validate-methods.c
new file mode 100644
index 00000000000..d7e1506c2a1
--- /dev/null
+++ b/src/backend/libpq/auth-validate-methods.c
@@ -0,0 +1,140 @@
+/*-------------------------------------------------------------------------
+ *
+ * auth-validate-methods.c
+ *      Implementation of authentication credential validation methods
+ *
+ * This module provides credential validation methods for various authentication
+ * types during active PostgreSQL sessions. It includes validation for password
+ * expiry, OAuth token expiry, and can be extended to other authentication
+ * mechanisms.
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *      src/backend/libpq/auth-validate-methods.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "access/xact.h"
+#include "catalog/pg_authid.h"
+#include "catalog/catalog.h"
+#include "libpq/auth-validate.h"
+#include "libpq/libpq-be.h"
+#include "libpq/oauth.h"
+#include "miscadmin.h"
+#include "storage/lmgr.h"
+#include "utils/syscache.h"
+#include "utils/timestamp.h"
+
+/* Function declarations for internal use */
+static bool validate_password_credentials(void);
+static bool validate_oauth_credentials(void);
+
+/* Function prototypes */
+void InitializeValidationMethods(void);
+
+/*
+ * Initialize validation methods
+ */
+void
+InitializeValidationMethods(void)
+{
+	/* Register all the validation methods */
+	RegisterCredentialValidator(CVT_PASSWORD, validate_password_credentials);
+	RegisterCredentialValidator(CVT_OAUTH, validate_oauth_credentials);
+}
+
+/*
+ * Validate password credentials by checking rolvaliduntil
+ * Returns true if credentials are still valid, false if they have expired.
+ */
+static bool
+validate_password_credentials(void)
+{
+	HeapTuple   tuple = NULL;
+	Datum       rolvaliduntil_datum;
+	bool        validuntil_null;
+	TimestampTz valid_until = 0;
+	TimestampTz current_time;
+	Oid         userid;
+	bool        result = false;
+
+	userid = GetSessionUserId();
+
+	/*
+	 * Try to take AccessShareLock on pg_authid to prevent concurrent modifications
+	 * from interfering with our validation. Use conditional acquisition to avoid
+	 * indefinite waiting during credential validation.
+	 */
+	if (!ConditionalLockRelationOid(AuthIdRelationId, AccessShareLock))
+	{
+		/*
+		 * Could not acquire lock immediately, which likely means another session
+		 * is modifying user data. For credential validation, it's better to
+		 * consider credentials valid and retry later than to block indefinitely.
+		 */
+		elog(LOG, "credential validation: could not acquire lock on pg_authid immediately, will retry later");
+		return true; /* Consider valid */
+	}
+
+	PG_TRY();
+	{
+		tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(userid));
+
+		if (HeapTupleIsValid(tuple))
+		{
+			/* Get the expiration time column */
+			rolvaliduntil_datum = SysCacheGetAttr(AUTHOID, tuple,
+												  Anum_pg_authid_rolvaliduntil,
+												  &validuntil_null);
+			if (!validuntil_null)
+			{
+				valid_until = DatumGetTimestampTz(rolvaliduntil_datum);
+				current_time = GetCurrentTimestamp();
+
+				result = !(valid_until < current_time);
+			}
+			else
+				result = true;
+
+			ReleaseSysCache(tuple);
+			tuple = NULL;
+		}
+	}
+	PG_CATCH();
+	{
+		if (tuple != NULL)
+			ReleaseSysCache(tuple);
+
+		UnlockRelationOid(AuthIdRelationId, AccessShareLock);
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	/* Release the relation lock */
+	UnlockRelationOid(AuthIdRelationId, AccessShareLock);
+
+	return result;
+}
+
+/*
+ * Check if an OAuth token has expired.
+ *
+ * Returns true if the token is still valid, false if it has expired.
+ *
+ * Calls wrapper CheckOAuthValidatorExpiration() function
+ * to verify that the token hasn't expired.
+ */
+static bool
+validate_oauth_credentials(void)
+{
+	/* Call the validator's expire_cb to check token expiration */
+	if (!CheckOAuthValidatorExpiration())
+		return false;
+
+	return true;
+}
diff --git a/src/backend/libpq/auth-validate.c b/src/backend/libpq/auth-validate.c
new file mode 100644
index 00000000000..82c475f6df0
--- /dev/null
+++ b/src/backend/libpq/auth-validate.c
@@ -0,0 +1,244 @@
+/*-------------------------------------------------------------------------
+*
+* auth-validate.c
+*      Implementation of authentication credential validation
+*
+* This module provides a mechanism for validating credentials during
+* an active PostgreSQL session.
+*
+* Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+* Portions Copyright (c) 1994, Regents of the University of California
+*
+* IDENTIFICATION
+*      src/backend/libpq/auth-validate.c
+*
+*-------------------------------------------------------------------------
+*/
+#include "postgres.h"
+
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "libpq/auth.h"
+#include "libpq/libpq-be.h"
+#include "libpq/auth-validate.h"
+#include "libpq/auth-validate-methods.h"
+#include "miscadmin.h"
+#include "postmaster/postmaster.h"
+#include "storage/ipc.h"
+#include "tcop/tcopprot.h"
+#include "utils/elog.h"
+#include "utils/guc.h"
+#include "utils/timestamp.h"
+#include "utils/timeout.h"
+
+/* GUC variables */
+bool		credential_validation_enabled;
+int			credential_validation_interval;
+
+/* Registered credential validators */
+static CredentialValidationCallback validators[CVT_COUNT];
+
+/*
+ * Convert UserAuth enum to CredentialValidationType for validator selection
+ */
+static CredentialValidationType
+UserAuthToValidationType(UserAuth auth_method)
+{
+	switch (auth_method)
+	{
+		case uaPassword:
+		case uaMD5:
+		case uaSCRAM:
+		/* All password-based methods use the password validator */
+			return CVT_PASSWORD;
+		case uaOAuth:
+			return CVT_OAUTH;
+		default:
+			/* No specific validator for other auth methods */
+			return CVT_COUNT;  /* Invalid value */
+	}
+}
+
+/*
+ * Process credential validation
+ */
+void
+ProcessCredentialValidation(void)
+{
+	/* Skip validation during initialization, bootstrap, authentication or connection setup */
+	if (ClientAuthInProgress || IsInitProcessingMode() || IsBootstrapProcessingMode())
+		return;
+
+	/* Check credentials if validation is enabled */
+	if (credential_validation_enabled && MyClientConnectionInfo.authn_id != NULL)
+	{
+		CredentialValidationStatus status;
+		UserAuth	auth_method = MyClientConnectionInfo.auth_method;
+
+		status = CheckCredentialValidity();
+
+		switch (status)
+		{
+			case CVS_VALID:
+				/* Credentials are valid, continue */
+				break;
+
+			case CVS_EXPIRED:
+				elog(LOG, "credential validation: credentials expired for auth_method=%d",
+					 (int) auth_method);
+				ereport(FATAL,
+						(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
+						 errmsg("session credentials have expired"),
+						 errhint("Please reconnect to establish a new authenticated session")));
+				break;
+
+			case CVS_ERROR:
+				elog(LOG, "credential validation: error checking credentials for auth_method=%d",
+					 (int) auth_method);
+				ereport(WARNING,
+						(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
+						 errmsg("error checking credential validity"),
+						 errhint("Credential validation will be retried at the next interval")));
+				break;
+			}
+	}
+}
+
+/*
+ * Initialize credential validation system Called from InitPostgres after
+ * authentication completes
+ */
+void
+InitializeCredentialValidation(void)
+{
+	int			i;
+
+	/* Define GUC variables */
+	DefineCustomBoolVariable("credential_validation.enabled",
+							 "Enable periodic credential validation.",
+							 NULL,
+							 &credential_validation_enabled,
+							 false,
+							 PGC_SUSET,
+							 0,
+							 NULL,
+							 NULL,
+							 NULL);
+
+	DefineCustomIntVariable("credential_validation.interval",
+							"Credential validation interval in minutes.",
+							NULL,
+							&credential_validation_interval,
+							1,	/* default: 1 minute */
+							1,	/* min: 1 minute */
+							60,	/* max: 60 minutes */
+							PGC_SUSET,
+							GUC_UNIT_MIN,
+							NULL,
+							NULL,
+							NULL);
+
+	/* Initialize validator callbacks to NULL */
+	for (i = 0; i < CVT_COUNT; i++)
+		validators[i] = NULL;
+
+	/* Initialize and register all validation methods */
+	InitializeValidationMethods();
+}
+
+/*
+ * Register a validator callback for a specific authentication method
+ */
+void
+RegisterCredentialValidator(CredentialValidationType method_type, CredentialValidationCallback validator)
+{
+	if (method_type < 0 || method_type >= CVT_COUNT)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid validation method type: %d", method_type)));
+
+	validators[method_type] = validator;
+}
+
+/*
+ * Check credential validity using the appropriate validator
+ */
+CredentialValidationStatus
+CheckCredentialValidity(void)
+{
+	CredentialValidationCallback validator = NULL;
+	CredentialValidationStatus status;
+
+	/*
+	 * Skip validation for:
+	 * - During shutdown or recovery
+	 * - Non-client backends (any process not serving a client connection)
+	 * - AutoVacuum processes (launcher and workers)
+	 * - Background worker processes
+	 * - Authentication is in progress
+	 */
+	if (proc_exit_inprogress ||
+		RecoveryInProgress() ||
+		!IsExternalConnectionBackend(MyBackendType) ||
+		AmAutoVacuumLauncherProcess() ||
+		AmAutoVacuumWorkerProcess() ||
+		AmBackgroundWorkerProcess() ||
+		ClientAuthInProgress)
+		return CVS_VALID;
+	/*
+	 * Use the session's authentication method from MyClientConnectionInfo
+	 * to select the appropriate validator.
+	 */
+	if (MyClientConnectionInfo.authn_id != NULL)
+	{
+		CredentialValidationType validation_type;
+
+		validation_type = UserAuthToValidationType(MyClientConnectionInfo.auth_method);
+
+		/*
+		 * If we have a valid validation type, get the corresponding
+		 * validator
+		 */
+		if (validation_type < CVT_COUNT)
+			validator = validators[validation_type];
+
+	}
+
+	/*
+	 * If no validator found for the current auth method or no
+	 * authenticated session, skip validation and consider credentials
+	 * valid
+	 */
+	if (validator == NULL || !MyClientConnectionInfo.authn_id)
+			return CVS_VALID;
+
+	/* Call the validator and interpret result */
+	PG_TRY();
+	{
+		bool		result;
+
+		elog(DEBUG1, "credential validation: calling validator for auth_method=%d",
+			 (int) MyClientConnectionInfo.auth_method);
+
+		result = validator();
+
+		if (!result)
+		{
+			elog(DEBUG1, "credential validation: credentials expired");
+			status = CVS_EXPIRED;	/* Validator reports credentials expired */
+		}
+		else
+			status = CVS_VALID;
+
+		return status;
+	}
+	PG_CATCH();
+	{
+		/* Error during validation */
+		elog(DEBUG1, "credential validation: error during validation");
+
+		FlushErrorState();
+		return CVS_ERROR;
+	}
+	PG_END_TRY();
+}
diff --git a/src/backend/libpq/meson.build b/src/backend/libpq/meson.build
index ee337cf42cc..608d9e10eb0 100644
--- a/src/backend/libpq/meson.build
+++ b/src/backend/libpq/meson.build
@@ -4,6 +4,8 @@ backend_sources += files(
   'auth-oauth.c',
   'auth-sasl.c',
   'auth-scram.c',
+  'auth-validate-methods.c',
+  'auth-validate.c',
   'auth.c',
   'be-fsstubs.c',
   'be-secure-common.c',
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index d01a09dd0c4..7307660e85a 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -44,6 +44,7 @@
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqsignal.h"
+#include "libpq/auth-validate.h"
 #include "mb/pg_wchar.h"
 #include "mb/stringinfo_mb.h"
 #include "miscadmin.h"
@@ -97,6 +98,49 @@ bool		Log_disconnections = false;
 
 int			log_statement = LOGSTMT_NONE;
 
+
+
+/*
+ * Function that performs credential validation when needed
+ * Uses a time-based approach to periodically validate credentials
+ * during normal operation, skipping validation in bootstrapping.
+ */
+static void
+CheckAndExecuteCredentialValidation(void)
+{
+	TimestampTz now;
+	TimestampTz diff;
+
+	/* Fast early returns for all cases where we should skip validation */
+	if (IsInitProcessingMode() || IsBootstrapProcessingMode())
+		return;
+
+	/* Get the current time */
+	now = GetCurrentTimestamp();
+
+	/* Use direct timestamp comparison for better performance */
+	if (LastCredentialValidationTime != 0)
+	{
+		int64 interval_us;
+
+		diff = now - LastCredentialValidationTime;
+		interval_us = (int64) credential_validation_interval * 60 * INT64CONST(1000000); /* minutes to microseconds */
+
+		/* Exit early if not enough time has passed */
+		if (diff < interval_us)
+			return;
+	}
+
+	/* Process credential validation */
+	ProcessCredentialValidation();
+
+	/* Update the last validation time */
+	LastCredentialValidationTime = now;
+
+	/* Only log at DEBUG level to reduce noise */
+	elog(DEBUG1, "Credential validation completed successfully");
+}
+
 /* wait N seconds to allow attach from a debugger */
 int			PostAuthDelay = 0;
 
@@ -1049,6 +1093,10 @@ exec_simple_query(const char *query_string)
 	 */
 	start_xact_command();
 
+	/* Check and potentially execute credential validation using time-based approach */
+	if (credential_validation_enabled && credential_validation_interval > 0 && IsNormalProcessingMode())
+		CheckAndExecuteCredentialValidation();
+
 	/*
 	 * Zap any pre-existing unnamed statement.  (While not strictly necessary,
 	 * it seems best to define simple-Query mode as if it used the unnamed
@@ -1430,6 +1478,10 @@ exec_parse_message(const char *query_string,	/* string to execute */
 	 */
 	start_xact_command();
 
+	/* Check and potentially execute credential validation for extended protocol */
+	if (credential_validation_enabled && credential_validation_interval > 0 && IsNormalProcessingMode())
+		CheckAndExecuteCredentialValidation();
+
 	/*
 	 * Switch to appropriate context for constructing parsetrees.
 	 *
@@ -1705,6 +1757,10 @@ exec_bind_message(StringInfo input_message)
 	 */
 	start_xact_command();
 
+	/* Check and potentially execute credential validation for extended protocol */
+	if (credential_validation_enabled && credential_validation_interval > 0 && IsNormalProcessingMode())
+		CheckAndExecuteCredentialValidation();
+
 	/* Switch back to message context */
 	MemoryContextSwitchTo(MessageContext);
 
@@ -2217,6 +2273,10 @@ exec_execute_message(const char *portal_name, long max_rows)
 	 */
 	start_xact_command();
 
+	/* Check and potentially execute credential validation for extended protocol */
+	if (credential_validation_enabled && credential_validation_interval > 0 && IsNormalProcessingMode())
+		CheckAndExecuteCredentialValidation();
+
 	/*
 	 * If we re-issue an Execute protocol request against an existing portal,
 	 * then we are only fetching more rows rather than completely re-executing
@@ -2635,6 +2695,10 @@ exec_describe_statement_message(const char *stmt_name)
 	 */
 	start_xact_command();
 
+	/* Check and potentially execute credential validation for extended protocol */
+	if (credential_validation_enabled && credential_validation_interval > 0 && IsNormalProcessingMode())
+		CheckAndExecuteCredentialValidation();
+
 	/* Switch back to message context */
 	MemoryContextSwitchTo(MessageContext);
 
@@ -2727,6 +2791,10 @@ exec_describe_portal_message(const char *portal_name)
 	 */
 	start_xact_command();
 
+	/* Check and potentially execute credential validation for extended protocol */
+	if (credential_validation_enabled && credential_validation_interval > 0 && IsNormalProcessingMode())
+		CheckAndExecuteCredentialValidation();
+
 	/* Switch back to message context */
 	MemoryContextSwitchTo(MessageContext);
 
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 36ad708b360..45beb71ef22 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -34,6 +34,7 @@ volatile sig_atomic_t QueryCancelPending = false;
 volatile sig_atomic_t ProcDiePending = false;
 volatile sig_atomic_t CheckClientConnectionPending = false;
 volatile sig_atomic_t ClientConnectionLost = false;
+TimestampTz LastCredentialValidationTime = 0;
 volatile sig_atomic_t IdleInTransactionSessionTimeoutPending = false;
 volatile sig_atomic_t TransactionTimeoutPending = false;
 volatile sig_atomic_t IdleSessionTimeoutPending = false;
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index b59e08605cc..138fd440600 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_db_role_setting.h"
 #include "catalog/pg_tablespace.h"
 #include "libpq/auth.h"
+#include "libpq/auth-validate.h"
 #include "libpq/libpq-be.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
@@ -1226,6 +1227,9 @@ InitPostgres(const char *in_dbname, Oid dboid,
 	/* Initialize this backend's session state. */
 	InitializeSession();
 
+	/* Initialize credential validation system */
+	InitializeCredentialValidation();
+
 	/*
 	 * If this is an interactive session, load any libraries that should be
 	 * preloaded at backend start.  Since those are determined by GUCs, this
@@ -1440,6 +1444,7 @@ ClientCheckTimeoutHandler(void)
 	SetLatch(MyLatch);
 }
 
+
 /*
  * Returns true if at least one role is defined in this database cluster.
  */
diff --git a/src/include/libpq/auth-validate-methods.h b/src/include/libpq/auth-validate-methods.h
new file mode 100644
index 00000000000..420183a1c7d
--- /dev/null
+++ b/src/include/libpq/auth-validate-methods.h
@@ -0,0 +1,25 @@
+/*-------------------------------------------------------------------------
+ *
+ * auth-validate-methods.h
+ *      Interface for authentication credential validation methods
+ *
+ * This file provides declarations for various credential validation methods
+ * used with the credential validation system.
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/libpq/auth-validate-methods.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef AUTH_VALIDATE_METHODS_H
+#define AUTH_VALIDATE_METHODS_H
+
+#include "libpq/libpq-be.h"
+#include "utils/timestamp.h"
+
+/* Initialize all validation methods */
+extern void InitializeValidationMethods(void);
+
+#endif                          /* AUTH_VALIDATE_METHODS_H */
diff --git a/src/include/libpq/auth-validate.h b/src/include/libpq/auth-validate.h
new file mode 100644
index 00000000000..52b17952744
--- /dev/null
+++ b/src/include/libpq/auth-validate.h
@@ -0,0 +1,61 @@
+/*-------------------------------------------------------------------------
+ *
+ * auth-validate.h
+ *	  Interface for authentication credential validation
+ *
+ * This file provides a common interface for validating credentials
+ * during an active PostgreSQL session.
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/libpq/auth-validate.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef AUTH_VALIDATE_H
+#define AUTH_VALIDATE_H
+
+#include "libpq/libpq-be.h"
+#include "libpq/protocol.h"
+#include "postmaster/postmaster.h"
+#include "utils/guc.h"
+#include "utils/timeout.h"
+
+/* Define credential validation method types as an enum */
+typedef enum CredentialValidationType
+{
+	CVT_PASSWORD = 0,          /* All password-based methods (md5, scram, etc) */
+	CVT_OAUTH,                 /* OAuth bearer token authentication */
+	CVT_COUNT                  /* Total number of credential validation types */
+} CredentialValidationType;
+
+/* Process credential validation */
+extern void ProcessCredentialValidation(void);
+
+/* GUC variables */
+extern PGDLLIMPORT bool credential_validation_enabled;
+extern PGDLLIMPORT int credential_validation_interval;
+
+/* Common credential validation callback prototype */
+typedef bool (*CredentialValidationCallback) (void);
+
+/* Credential validation status */
+typedef enum CredentialValidationStatus
+{
+	CVS_VALID,					/* Credentials are valid */
+	CVS_EXPIRED,				/* Credentials have expired */
+	CVS_ERROR					/* Error during validation */
+} CredentialValidationStatus;
+
+/* Initialize credential validation system */
+extern void InitializeCredentialValidation(void);
+
+/* Register a validation callback for a specific authentication method */
+extern void RegisterCredentialValidator(CredentialValidationType method_type,
+										CredentialValidationCallback validator);
+
+/* Check credential validity */
+extern CredentialValidationStatus CheckCredentialValidity(void);
+
+#endif							/* AUTH_VALIDATE_H */
diff --git a/src/include/libpq/oauth.h b/src/include/libpq/oauth.h
index 4a822e9a1f2..bbb9290626c 100644
--- a/src/include/libpq/oauth.h
+++ b/src/include/libpq/oauth.h
@@ -64,6 +64,7 @@ typedef void (*ValidatorShutdownCB) (ValidatorModuleState *state);
 typedef bool (*ValidatorValidateCB) (const ValidatorModuleState *state,
 									 const char *token, const char *role,
 									 ValidatorModuleResult *result);
+typedef bool (*ValidatorExpireCB) (const ValidatorModuleState *state);
 
 /*
  * Identifies the compiled ABI version of the validator module. Since the server
@@ -80,6 +81,7 @@ typedef struct OAuthValidatorCallbacks
 	ValidatorStartupCB startup_cb;
 	ValidatorShutdownCB shutdown_cb;
 	ValidatorValidateCB validate_cb;
+	ValidatorExpireCB expire_cb;  /* Optional: Check token expiration */
 } OAuthValidatorCallbacks;
 
 /*
@@ -98,4 +100,8 @@ extern PGDLLIMPORT const pg_be_sasl_mech pg_be_oauth_mech;
  */
 extern bool check_oauth_validator(HbaLine *hbaline, int elevel, char **err_msg);
 
+/*
+ * Check OAuth token expiration using validator's expire_cb if available.
+ */
+bool CheckOAuthValidatorExpiration(void);
 #endif							/* PG_OAUTH_H */
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index f16f35659b9..30c3d40d418 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -99,6 +99,7 @@ extern PGDLLIMPORT volatile sig_atomic_t IdleStatsUpdateTimeoutPending;
 
 extern PGDLLIMPORT volatile sig_atomic_t CheckClientConnectionPending;
 extern PGDLLIMPORT volatile sig_atomic_t ClientConnectionLost;
+extern PGDLLIMPORT TimestampTz LastCredentialValidationTime;
 
 /* these are marked volatile because they are examined by signal handlers: */
 extern PGDLLIMPORT volatile uint32 InterruptHoldoffCount;


view thread (11+ messages)  latest in thread

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: [email protected]
  Cc: [email protected], [email protected], [email protected], [email protected], [email protected]
  Subject: Re: [OAuth2] Infrastructure for tracking token expiry time
  In-Reply-To: <CAER375OVXtyp7GiYNes30jn_pOrXoC_qDEEVGS2aDErm4oKiYQ@mail.gmail.com>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox