libpq implements support for the OAuth v2 Device Authorization client flow, documented in RFC 8628, which it will attempt to use by default if the server requests a bearer token during authentication. This flow can be utilized even if the system running the client application does not have a usable web browser, for example when running a client via SSH. Client applications may implement their own flows instead; see Section 32.20.1.
The builtin flow will, by default, print a URL to visit and a user code to enter there:
$ psql 'dbname=postgres oauth_issuer=https://example.com oauth_client_id=...' Visit https://example.com/device and enter the code: ABCD-EFGH
(This prompt may be customized.) The user will then log into their OAuth provider, which will ask whether to allow libpq and the server to perform actions on their behalf. It is always a good idea to carefully review the URL and permissions displayed, to ensure they match expectations, before continuing. Permissions should not be given to untrusted third parties.
For an OAuth client flow to be usable, the connection string must at minimum contain oauth_issuer and oauth_client_id. (These settings are determined by your organization's OAuth provider.) The builtin flow additionally requires the OAuth authorization server to publish a device authorization endpoint.
The builtin Device Authorization flow is not currently supported on Windows. Custom client flows may still be implemented.
The behavior of the OAuth flow may be modified or replaced by a client using the following hook API:
PQsetAuthDataHook
#Sets the PGauthDataHook
, overriding libpq's handling of one or more aspects of its OAuth client flow.
void PQsetAuthDataHook(PQauthDataHook_type hook);
If hook
is NULL
, the default handler will be reinstalled. Otherwise, the application passes a pointer to a callback function with the signature:
int hook_fn(PGauthData type, PGconn *conn, void *data);
which libpq will call when an action is required of the application. type
describes the request being made, conn
is the connection handle being authenticated, and data
points to request-specific metadata. The contents of this pointer are determined by type
; see Section 32.20.1.1 for the supported list.
Hooks can be chained together to allow cooperative and/or fallback behavior. In general, a hook implementation should examine the incoming type
(and, potentially, the request metadata and/or the settings for the particular conn
in use) to decide whether or not to handle a specific piece of authdata. If not, it should delegate to the previous hook in the chain (retrievable via PQgetAuthDataHook
).
Success is indicated by returning an integer greater than zero. Returning a negative integer signals an error condition and abandons the connection attempt. (A zero value is reserved for the default implementation.)
PQgetAuthDataHook
#Retrieves the current value of PGauthDataHook
.
PQauthDataHook_type PQgetAuthDataHook(void);
At initialization time (before the first call to PQsetAuthDataHook
), this function will return PQdefaultAuthDataHook
.
The following PGauthData
types and their corresponding data
structures are defined:
PQAUTHDATA_PROMPT_OAUTH_DEVICE
#Replaces the default user prompt during the builtin device authorization client flow. data
points to an instance of PGpromptOAuthDevice
:
typedef struct _PGpromptOAuthDevice { const char *verification_uri; /* verification URI to visit */ const char *user_code; /* user code to enter */ const char *verification_uri_complete; /* optional combination of URI and * code, or NULL */ int expires_in; /* seconds until user code expires */ } PGpromptOAuthDevice;
The OAuth Device Authorization flow included in libpq requires the end user to visit a URL with a browser, then enter a code which permits libpq to connect to the server on their behalf. The default prompt simply prints the verification_uri
and user_code
on standard error. Replacement implementations may display this information using any preferred method, for example with a GUI.
This callback is only invoked during the builtin device authorization flow. If the application installs a custom OAuth flow, this authdata type will not be used.
If a non-NULL verification_uri_complete
is provided, it may optionally be used for non-textual verification (for example, by displaying a QR code). The URL and user code should still be displayed to the end user in this case, because the code will be manually confirmed by the provider, and the URL lets users continue even if they can't use the non-textual method. For more information, see section 3.3.1 in RFC 8628.
PQAUTHDATA_OAUTH_BEARER_TOKEN
#Replaces the entire OAuth flow with a custom implementation. The hook should either directly return a Bearer token for the current user/issuer/scope combination, if one is available without blocking, or else set up an asynchronous callback to retrieve one.
data
points to an instance of PGoauthBearerRequest
, which should be filled in by the implementation:
typedef struct PGoauthBearerRequest { /* Hook inputs (constant across all calls) */ const char *openid_configuration; /* OIDC discovery URL */ const char *scope; /* required scope(s), or NULL */ /* Hook outputs */ /* Callback implementing a custom asynchronous OAuth flow. */ PostgresPollingStatusType (*async) (PGconn *conn, struct PGoauthBearerRequest *request, SOCKTYPE *altsock); /* Callback to clean up custom allocations. */ void (*cleanup) (PGconn *conn, struct PGoauthBearerRequest *request); char *token; /* acquired Bearer token */ void *user; /* hook-defined allocated data */ } PGoauthBearerRequest;
Two pieces of information are provided to the hook by libpq: openid_configuration
contains the URL of an OAuth discovery document describing the authorization server's supported flows, and scope
contains a (possibly empty) space-separated list of OAuth scopes which are required to access the server. Either or both may be NULL
to indicate that the information was not discoverable. (In this case, implementations may be able to establish the requirements using some other preconfigured knowledge, or they may choose to fail.)
The final output of the hook is token
, which must point to a valid Bearer token for use on the connection. (This token should be issued by the oauth_issuer and hold the requested scopes, or the connection will be rejected by the server's validator module.) The allocated token string must remain valid until libpq is finished connecting; the hook should set a cleanup
callback which will be called when libpq no longer requires it.
If an implementation cannot immediately produce a token
during the initial call to the hook, it should set the async
callback to handle nonblocking communication with the authorization server. [16] This will be called to begin the flow immediately upon return from the hook. When the callback cannot make further progress without blocking, it should return either PGRES_POLLING_READING
or PGRES_POLLING_WRITING
after setting *pgsocket
to the file descriptor that will be marked ready to read/write when progress can be made again. (This descriptor is then provided to the top-level polling loop via PQsocket()
.) Return PGRES_POLLING_OK
after setting token
when the flow is complete, or PGRES_POLLING_FAILED
to indicate failure.
Implementations may wish to store additional data for bookkeeping across calls to the async
and cleanup
callbacks. The user
pointer is provided for this purpose; libpq will not touch its contents and the application may use it at its convenience. (Remember to free any allocations during token cleanup.)
A "dangerous debugging mode" may be enabled by setting the environment variable PGOAUTHDEBUG=UNSAFE
. This functionality is provided for ease of local development and testing only. It does several things that you will not want a production system to do:
permits the use of unencrypted HTTP during the OAuth provider exchange
allows the system's trusted CA list to be completely replaced using the PGOAUTHCAFILE
environment variable
prints HTTP traffic (containing several critical secrets) to standard error during the OAuth flow
permits the use of zero-second retry intervals, which can cause the client to busy-loop and pointlessly consume CPU
Do not share the output of the OAuth flow traffic with third parties. It contains secrets that can be used to attack your clients and servers.
[16] Performing blocking operations during the PQAUTHDATA_OAUTH_BEARER_TOKEN
hook callback will interfere with nonblocking connection APIs such as PQconnectPoll
and prevent concurrent connections from making progress. Applications which only ever use the synchronous connection primitives, such as PQconnectdb
, may synchronously retrieve a token during the hook instead of implementing the async
callback, but they will necessarily be limited to one connection at a time.