<!-- doc/src/sgml/oauth-validators.sgml -->

<chapter id="oauth-validators">
 <title>OAuth Validator Modules</title>
 <indexterm zone="oauth-validators">
  <primary>OAuth Validators</primary>
 </indexterm>
 <para>
  <productname>PostgreSQL</productname> provides infrastructure for creating
  custom modules to perform server-side validation of OAuth bearer tokens.
  Because OAuth implementations vary so wildly, and bearer token validation is
  heavily dependent on the issuing party, the server cannot check the token
  itself; validator modules provide the integration layer between the server
  and the OAuth provider in use.
 </para>
 <para>
  OAuth validator modules must at least consist of an initialization function
  (see <xref linkend="oauth-validator-init"/>) and the required callback for
  performing validation (see <xref linkend="oauth-validator-callback-validate"/>).
 </para>
 <warning>
  <para>
   Since a misbehaving validator might let unauthorized users into the database,
   correct implementation is crucial for server safety. See
   <xref linkend="oauth-validator-design"/> for design considerations.
  </para>
 </warning>

 <sect1 id="oauth-validator-design">
  <title>Safely Designing a Validator Module</title>
  <warning>
   <para>
    Read and understand the entirety of this section before implementing a
    validator module. A malfunctioning validator is potentially worse than no
    authentication at all, both because of the false sense of security it
    provides, and because it may contribute to attacks against other pieces of
    an OAuth ecosystem.
   </para>
  </warning>

  <sect2 id="oauth-validator-design-responsibilities">
   <title>Validator Responsibilities</title>
   <para>
    Although different modules may take very different approaches to token
    validation, implementations generally need to perform three separate
    actions:
   </para>
   <variablelist>
    <varlistentry>
     <term>Validate the Token</term>
     <listitem>
      <para>
       The validator must first ensure that the presented token is in fact a
       valid Bearer token for use in client authentication. The correct way to
       do this depends on the provider, but it generally involves either
       cryptographic operations to prove that the token was created by a trusted
       party (offline validation), or the presentation of the token to that
       trusted party so that it can perform validation for you (online
       validation).
      </para>
      <para>
       Online validation, usually implemented via
       <ulink url="https://datatracker.ietf.org/doc/html/rfc7662">OAuth Token
       Introspection</ulink>, requires fewer steps of a validator module and
       allows central revocation of a token in the event that it is stolen
       or misissued. However, it does require the module to make at least one
       network call per authentication attempt (all of which must complete
       within the configured <xref linkend="guc-authentication-timeout"/>).
       Additionally, your provider may not provide introspection endpoints for
       use by external resource servers.
      </para>
      <para>
       Offline validation is much more involved, typically requiring a validator
       to maintain a list of trusted signing keys for a provider and then
       check the token's cryptographic signature along with its contents.
       Implementations must follow the provider's instructions to the letter,
       including any verification of issuer ("where is this token from?"),
       audience ("who is this token for?"), and validity period ("when can this
       token be used?"). Since there is no communication between the module and
       the provider, tokens cannot be centrally revoked using this method;
       offline validator implementations may wish to place restrictions on the
       maximum length of a token's validity period.
      </para>
      <para>
       If the token cannot be validated, the module should immediately fail.
       Further authentication/authorization is pointless if the bearer token
       wasn't issued by a trusted party.
      </para>
     </listitem>
    </varlistentry>
    <varlistentry>
     <term>Authorize the Client</term>
     <listitem>
      <para>
       Next the validator must ensure that the end user has given the client
       permission to access the server on their behalf. This generally involves
       checking the scopes that have been assigned to the token, to make sure
       that they cover database access for the current HBA parameters.
      </para>
      <para>
       The purpose of this step is to prevent an OAuth client from obtaining a
       token under false pretenses. If the validator requires all tokens to
       carry scopes that cover database access, the provider should then loudly
       prompt the user to grant that access during the flow. This gives them the
       opportunity to reject the request if the client isn't supposed to be
       using their credentials to connect to databases.
      </para>
      <para>
       While it is possible to establish client authorization without explicit
       scopes by using out-of-band knowledge of the deployed architecture, doing
       so removes the user from the loop, which prevents them from catching
       deployment mistakes and allows any such mistakes to be exploited
       silently. Access to the database must be tightly restricted to only
       trusted clients
       <footnote>
        <para>
         That is, "trusted" in the sense that the OAuth client and the
         <productname>PostgreSQL</productname> server are controlled by the same
         entity. Notably, the Device Authorization client flow supported by
         libpq does not usually meet this bar, since it's designed for use by
         public/untrusted clients.
        </para>
       </footnote>
       if users are not prompted for additional scopes.
      </para>
      <para>
       Even if authorization fails, a module may choose to continue to pull
       authentication information from the token for use in auditing and
       debugging.
      </para>
     </listitem>
    </varlistentry>
    <varlistentry>
     <term>Authenticate the End User</term>
     <listitem>
      <para>
       Finally, the validator should determine a user identifier for the token,
       either by asking the provider for this information or by extracting it
       from the token itself, and return that identifier to the server (which
       will then make a final authorization decision using the HBA
       configuration). This identifier will be available within the session via
       <link linkend="functions-info-session-table"><function>system_user</function></link>
       and recorded in the server logs if <xref linkend="guc-log-connections"/>
       is enabled.
      </para>
      <para>
       Different providers may record a variety of different authentication
       information for an end user, typically referred to as
       <emphasis>claims</emphasis>. Providers usually document which of these
       claims are trustworthy enough to use for authorization decisions and
       which are not. (For instance, it would probably not be wise to use an
       end user's full name as the identifier for authentication, since many
       providers allow users to change their display names arbitrarily.)
       Ultimately, the choice of which claim (or combination of claims) to use
       comes down to the provider implementation and application requirements.
      </para>
      <para>
       Note that anonymous/pseudonymous login is possible as well, by enabling
       usermap delegation; see
       <xref linkend="oauth-validator-design-usermap-delegation"/>.
      </para>
     </listitem>
    </varlistentry>
   </variablelist>
  </sect2>

  <sect2 id="oauth-validator-design-guidelines">
   <title>General Coding Guidelines</title>
   <para>
    Developers should keep the following in mind when implementing token
    validation:
   </para>
   <variablelist>
    <varlistentry>
     <term>Token Confidentiality</term>
     <listitem>
      <para>
       Modules should not write tokens, or pieces of tokens, into the server
       log. This is true even if the module considers the token invalid; an
       attacker who confuses a client into communicating with the wrong provider
       should not be able to retrieve that (otherwise valid) token from the
       disk.
      </para>
      <para>
       Implementations that send tokens over the network (for example, to
       perform online token validation with a provider) must authenticate the
       peer and ensure that strong transport security is in use.
      </para>
     </listitem>
    </varlistentry>
    <varlistentry>
     <term>Logging</term>
     <listitem>
      <para>
       To simply log the reason for a validation failure, modules may set the
       freeform <structfield>error_detail</structfield> field during the
       <link linkend="oauth-validator-callback-validate">validate callback</link>.
       (<xref linkend="error-style-guide"/> has guidelines for writing good
       <literal>DETAIL</literal> messages.) <structfield>error_detail</structfield>
       is printed only to the server log, as part of the final authentication
       failure message, and it is not shared with the client.
      </para>
      <para>
       Modules may also use the same <link linkend="error-message-reporting">logging
       facilities</link> as standard extensions; however, the rules for emitting
       log entries to the client are subtly different during the authentication
       phase of the connection. Generally speaking, modules should log
       problems at the <symbol>COMMERROR</symbol> level and return
       normally, instead of using <symbol>ERROR</symbol>/<symbol>FATAL</symbol>
       to unwind the stack, to avoid leaking information to unauthenticated
       clients.
      </para>
     </listitem>
    </varlistentry>
    <varlistentry>
     <term>Interruptibility</term>
     <listitem>
      <para>
       Modules must remain interruptible by signals so that the server can
       correctly handle authentication timeouts and shutdown signals from
       <application>pg_ctl</application>. For example, blocking calls on sockets
       should generally be replaced with code that handles both socket events
       and interrupts without races (see <function>WaitLatchOrSocket()</function>,
       <function>WaitEventSetWait()</function>, et al), and long-running loops
       should periodically call <function>CHECK_FOR_INTERRUPTS()</function>.
       Failure to follow this guidance may result in unresponsive backend
       sessions.
      </para>
     </listitem>
    </varlistentry>
    <varlistentry>
     <term>Testing</term>
     <listitem>
      <para>
       The breadth of testing an OAuth system is well beyond the scope of this
       documentation, but at minimum, negative testing should be considered
       mandatory. It's trivial to design a module that lets authorized users in;
       the whole point of the system is to keep unauthorized users out.
      </para>
     </listitem>
    </varlistentry>
    <varlistentry>
     <term>Documentation</term>
     <listitem>
      <para>
       Validator implementations should document the contents and format of the
       authenticated ID that is reported to the server for each end user, since
       DBAs may need to use this information to construct pg_ident maps. (For
       instance, is it an email address? an organizational ID number? a UUID?)
       They should also document whether or not it is safe to use the module in
       <symbol>delegate_ident_mapping=1</symbol> mode, and what additional
       configuration is required in order to do so.
      </para>
      <para>
       If an implementation provides <link linkend="oauth-validator-hba">custom
       HBA options</link>, the names and syntax of those options should be
       documented as well.
      </para>
     </listitem>
    </varlistentry>
   </variablelist>
  </sect2>

  <sect2 id="oauth-validator-design-usermap-delegation">
   <title>Authorizing Users (Usermap Delegation)</title>
   <para>
    The standard deliverable of a validation module is the user identifier,
    which the server will then compare to any configured
    <link linkend="auth-username-maps"><filename>pg_ident.conf</filename>
    mappings</link> and determine whether the end user is authorized to connect.
    However, OAuth is itself an authorization framework, and tokens may carry
    information about user privileges. For example, a token may be associated
    with the organizational groups that a user belongs to, or list the roles
    that a user may assume, and duplicating that knowledge into local usermaps
    for every server may not be desirable.
   </para>
   <para>
    To bypass username mapping entirely, and have the validator module assume
    the additional responsibility of authorizing user connections, the HBA may
    be configured with <xref linkend="auth-oauth-delegate-ident-mapping"/>.
    The module may then use token scopes or an equivalent method to decide
    whether the user is allowed to connect under their desired role. The user
    identifier will still be recorded by the server, but it plays no part in
    determining whether to continue the connection.
   </para>
   <para>
    Using this scheme, authentication itself is optional. As long as the module
    reports that the connection is authorized, login will continue even if there
    is no recorded user identifier at all. This makes it possible to implement
    anonymous or pseudonymous access to the database, where the third-party
    provider performs all necessary authentication but does not provide any
    user-identifying information to the server. (Some providers may create an
    anonymized ID number that can be recorded instead, for later auditing.)
   </para>
   <para>
    Usermap delegation provides the most architectural flexibility, but it turns
    the validator module into a single point of failure for connection
    authorization. Use with caution.
   </para>
  </sect2>
 </sect1>

 <sect1 id="oauth-validator-init">
  <title>Initialization Functions</title>
  <indexterm zone="oauth-validator-init">
   <primary>_PG_oauth_validator_module_init</primary>
  </indexterm>
  <para>
   OAuth validator modules are dynamically loaded from the shared
   libraries listed in <xref linkend="guc-oauth-validator-libraries"/>.
   Modules are loaded on demand when requested from a login in progress.
   The normal library search path is used to locate the library. To
   provide the validator callbacks and to indicate that the library is an OAuth
   validator module a function named
   <function>_PG_oauth_validator_module_init</function> must be provided. The
   return value of the function must be a pointer to a struct of type
   <structname>OAuthValidatorCallbacks</structname>, which contains a magic
   number and pointers to the module's token validation functions. The returned
   pointer must be of server lifetime, which is typically achieved by defining
   it as a <literal>static const</literal> variable in global scope.
<programlisting>
typedef struct OAuthValidatorCallbacks
{
    uint32        magic;            /* must be set to PG_OAUTH_VALIDATOR_MAGIC */

    ValidatorStartupCB startup_cb;
    ValidatorShutdownCB shutdown_cb;
    ValidatorValidateCB validate_cb;
} OAuthValidatorCallbacks;

typedef const OAuthValidatorCallbacks *(*OAuthValidatorModuleInit) (void);
</programlisting>

   Only the <function>validate_cb</function> callback is required, the others
   are optional.
  </para>
 </sect1>

 <sect1 id="oauth-validator-callbacks">
  <title>OAuth Validator Callbacks</title>
  <para>
   OAuth validator modules implement their functionality by defining a set of
   callbacks. The server will call them as required to process the
   authentication request from the user.
  </para>

  <sect2 id="oauth-validator-callback-startup">
   <title>Startup Callback</title>
   <para>
    The <function>startup_cb</function> callback is executed directly after
    loading the module. This callback can be used to set up local state,
    define <link linkend="oauth-validator-hba">custom HBA options</link>, and
    perform additional initialization if required. If the validator module
    has state it can use <structfield>state->private_data</structfield> to
    store it.

<programlisting>
typedef void (*ValidatorStartupCB) (ValidatorModuleState *state);
</programlisting>
   </para>
  </sect2>

  <sect2 id="oauth-validator-callback-validate">
   <title>Validate Callback</title>
   <para>
    The <function>validate_cb</function> callback is executed during the OAuth
    exchange when a user attempts to authenticate using OAuth.  Any state set in
    previous calls will be available in <structfield>state->private_data</structfield>.

<programlisting>
typedef bool (*ValidatorValidateCB) (const ValidatorModuleState *state,
                                     const char *token, const char *role,
                                     ValidatorModuleResult *result);
</programlisting>

    <replaceable>token</replaceable> will contain the bearer token to validate.
    <application>PostgreSQL</application> has ensured that the token is well-formed syntactically, but no
    other validation has been performed.  <replaceable>role</replaceable> will
    contain the role the user has requested to log in as.  The callback must
    set output parameters in the <literal>result</literal> struct, which is
    defined as below:

<programlisting>
typedef struct ValidatorModuleResult
{
    bool        authorized;
    char       *authn_id;
    char       *error_detail;
} ValidatorModuleResult;
</programlisting>

    The connection will only proceed if the module sets
    <structfield>result->authorized</structfield> to <literal>true</literal>.  To
    authenticate the user, the authenticated user name (as determined using the
    token) shall be palloc'd and returned in the <structfield>result->authn_id</structfield>
    field.  Alternatively, <structfield>result->authn_id</structfield> may be set to
    NULL if the token is valid but the associated user identity cannot be
    determined.  If the validator returns <literal>true</literal> and
    sets <structfield>result->authn_id</structfield> then the identity appears
    in the server log when <xref linkend="guc-log-connections"/> includes
    <literal>authentication</literal>.  This happens before authorization and
    will log authentication even if the connection is later rejected due to
    authorization.
   </para>
   <para>
    A validator may return <literal>false</literal> to signal an internal error,
    in which case the connection fails.  Otherwise the validator should return
    <literal>true</literal> to indicate that it has processed the token and made
    an authorization decision.
   </para>
   <para>
    In either failure case (validation error or internal error) the module may
    store a user-readable reason for the failure in <structfield>result->error_detail</structfield>.
    This will be printed to the server logs (not sent to the client) as a
    <literal>DETAIL</literal> entry for the authentication failure. The memory
    pointed to by <structfield>error_detail</structfield> may be either palloc'd
    or of static duration. <structfield>error_detail</structfield> is ignored
    on success.
   </para>
   <para>
    The behavior after <function>validate_cb</function> returns depends on the
    specific HBA setup.  Normally, the <structfield>result->authn_id</structfield> user
    name must exactly match the role that the user is logging in as.  (This
    behavior may be modified with a usermap.)  But when authenticating against
    an HBA rule with <literal>delegate_ident_mapping</literal> turned on,
    <productname>PostgreSQL</productname> will not perform any checks on the value of
    <structfield>result->authn_id</structfield> at all; in this case it is up to the
    validator to ensure that the token carries enough privileges for the user to
    log in under the indicated <replaceable>role</replaceable>.
   </para>
  </sect2>

  <sect2 id="oauth-validator-callback-shutdown">
   <title>Shutdown Callback</title>
   <para>
    The <function>shutdown_cb</function> callback is executed when the server
    backend has finished validating tokens for the connection. If the validator
    module has any allocated state, this callback should free it to avoid
    resource leaks.
<programlisting>
typedef void (*ValidatorShutdownCB) (ValidatorModuleState *state);
</programlisting>
   </para>
  </sect2>

 </sect1>

 <sect1 id="oauth-validator-hba">
  <title>Custom HBA Options</title>

  <para>
   Like other preloaded libraries, validator modules may define
   <link linkend="runtime-config-custom">custom GUC parameters</link> for user
   configuration in <filename>postgresql.conf</filename>. However, it may be
   desirable to configure behavior at a more granular level (say, for a
   particular issuer or a group of users) instead of globally.
  </para>

  <para>
   Beginning in <productname>PostgreSQL</productname> 19, validator
   implementations may define custom options for use inside
   <filename>pg_hba.conf</filename>. These options are then
   <link linkend="auth-oauth-validator-option">made available</link> to the user
   as <literal>validator.<replaceable>option</replaceable></literal>. The API
   for registering and retrieving custom options is described below.
  </para>

  <sect2 id="oauth-validator-hba-api">
   <title>Options API</title>
    <para>
     Modules register custom HBA option names during the <function>startup_cb</function>
     callback, using <function>RegisterOAuthHBAOptions()</function>:

<programlisting>
/*
 * Register a list of custom option names for use in pg_hba.conf. For each name
 * "foo" registered here, that option will be provided as "validator.foo" in
 * the HBA.
 *
 * Valid option names consist of alphanumeric ASCII, underscore (_), and hyphen
 * (-). Invalid option names will be ignored with a WARNING logged at
 * connection time.
 *
 * This function may only be called during the startup_cb callback. Multiple
 * calls are permitted, which will append to the existing list of registered
 * options; options cannot be unregistered.
 *
 * Parameters:
 *
 * - state: the state pointer passed to the startup_cb callback
 * - num:   the number of options in the opts array
 * - opts:  an array of null-terminated option names to register
 *
 * The list of option names is copied internally, and the opts array is not
 * required to remain valid after the call.
 */
void RegisterOAuthHBAOptions(ValidatorModuleState *state, int num,
                             const char *opts[]);
</programlisting>
    </para>

    <para>
     Each option's value, if set, may be later retrieved using
     <function>GetOAuthHBAOption()</function>:

<programlisting>
/*
 * Retrieve the string value of an HBA option which was registered via
 * RegisterOAuthHBAOptions(). Usable only during validate_cb or shutdown_cb.
 *
 * If the user has set the corresponding option in pg_hba.conf, this function
 * returns that value as a null-terminated string, which must not be modified
 * or freed. NULL is returned instead if the user has not set this option, if
 * the option name was not registered, or if this function is incorrectly called
 * during the startup_cb.
 *
 * Parameters:
 *
 * - state:   the state pointer passed to the validate_cb/shutdown_cb callback
 * - optname: the name of the option to retrieve
 */
const char *GetOAuthHBAOption(const ValidatorModuleState *state,
                              const char *optname);
</programlisting>
    </para>

    <para>
     See <xref linkend="oauth-validator-hba-example-usage"/> for sample usage.
    </para>
  </sect2>

  <sect2 id="oauth-validator-hba-limitations">
   <title>Limitations</title>
   <para>
    <itemizedlist>
     <listitem>
      <para>
       Option names are limited to ASCII alphanumeric characters,
       underscores (<literal>_</literal>), and hyphens (<literal>-</literal>).
      </para>
     </listitem>
     <listitem>
      <para>
       Option values are always freeform strings (in contrast to custom GUCs,
       which support numerics, booleans, and enums).
      </para>
     </listitem>
     <listitem>
      <para>
       Option names and values cannot be checked by the server during a reload of
       the configuration. Any unregistered options in <filename>pg_hba.conf</filename>
       will instead result in connection failures. It is the responsibility of
       each module to document and verify the syntax of option values as needed.
       <footnote>
        <para>
         If a module finds an invalid option value during <function>validate_cb</function>,
         it's recommended to <link linkend="oauth-validator-callback-validate">signal
         an internal error</link> by setting <structfield>result->error_detail</structfield>
         to a description of the problem and returning <literal>false</literal>.
        </para>
       </footnote>
      </para>
     </listitem>
    </itemizedlist>
   </para>
  </sect2>

  <sect2 id="oauth-validator-hba-example-usage">
   <title>Example Usage</title>

   <para>
    For a hypothetical module, the options <literal>foo</literal> and
    <literal>bar</literal> could be registered as follows:

<programlisting>
static void
validator_startup(ValidatorModuleState *state)
{
    static const char *opts[] = {
        "foo",      /* description of access privileges */
        "bar",      /* magic URL for additional administrator powers */
    };

    RegisterOAuthHBAOptions(state, lengthof(opts), opts);

    /* ...other setup... */
}
</programlisting>
   </para>

   <para>
    The following sample entries in <filename>pg_hba.conf</filename> can then
    make use of these options:

<programlisting>
# TYPE   DATABASE   USER   ADDRESS    METHOD
hostssl  postgres   admin  0.0.0.0/0  oauth issuer=https://admin.example.com \
                                            scope="pg-admin openid email" \
                                            map=oauth-email \
                                            validator.foo="admin access" \
                                            validator.bar=https://magic.example.com

hostssl  postgres   all    0.0.0.0/0  oauth issuer=https://www.example.com \
                                            scope="pg-user openid email" \
                                            map=oauth-email \
                                            validator.foo="user access"
</programlisting>
   </para>

   <para>
    The module can retrieve the option settings from the HBA during validation:

<programlisting>
static bool
validate_token(const ValidatorModuleState *state,
               const char *token, const char *role,
               ValidatorModuleResult *res)
{
    const char *foo = GetOAuthHBAOption(state, "foo"); /* "admin access" or "user access" */
    const char *bar = GetOAuthHBAOption(state, "bar"); /* "https://magic.example.com" or NULL */

    if (bar &amp;&amp; !is_valid_url(bar))
    {
        res->error_detail = psprintf("validator.bar (\"%s\") is not a valid URL.", bar);
        return false;
    }

    /* proceed to validate token */
}
</programlisting>
   </para>

   <para>
    When multiple validators are in use, their registered option lists remain
    independent:

<programlisting>
<lineannotation>in postgresql.conf:</lineannotation>
oauth_validator_libraries = 'example_org, my_validator'

<lineannotation>in pg_hba.conf:</lineannotation>
# TYPE   DATABASE   USER   ADDRESS    METHOD
hostssl  postgres   admin  0.0.0.0/0  oauth issuer=https://admin.example.com \
                                            scope="pg-admin openid email" \
                                            map=oauth-email \
                                            validator=my_validator \
                                            validator.foo="admin access" \
                                            validator.bar=https://magic.example.com

hostssl  postgres   all    0.0.0.0/0  oauth issuer=https://www.example.org \
                                            scope="pg-user openid profile" \
                                            validator=example_org \
                                            delegate_ident_mapping=1 \
                                            validator.magic=on \
                                            validator.more_magic=off
</programlisting>
   </para>
  </sect2>
 </sect1>
</chapter>
