Store Credentials with QgsAuthManager in PyQGIS
A PostGIS connection string with a password in it ends up in a project file, in version control and in a colleague's email. QGIS has a proper answer: an encrypted SQLite database of credentials, referenced from layer URIs by an opaque id, unlocked once per session by a master password. Wiring it up from Python is straightforward; making it work unattended takes one more step that is easy to get wrong.
This recipe belongs to Headless QGIS & Server Automation in PyQGIS. It covers initialising the auth manager, setting the master password without a prompt, creating configurations for basic and token authentication, referencing them from data source URIs, and the operational questions a shared credential store raises.
Prerequisites
- QGIS 3.34 LTR (bundled Python 3.12) or newer built with authentication support, which every standard build is.
- A writable user profile directory. The auth database lives there as
qgis-auth.db. - A decision about where the master password will come from in unattended use, before you start.
Initialise and unlock
Nothing works until the auth manager is initialised and the master password is set.
import os
from qgis.core import QgsApplication
manager = QgsApplication.authManager()
manager.setMasterPassword(os.environ["QGIS_AUTH_PASSWORD"], verify=True)
if not manager.masterPasswordIsSet():
raise RuntimeError("master password was not accepted")
Breakdown: verify=True checks the password against the existing database rather than silently accepting a wrong one and failing later on every lookup. On a fresh profile with no database, the first call sets the password, so a script running against a new profile creates the store — which is why a typo on first run is unrecoverable except by deleting the database. Reading it from an environment variable keeps it out of the source; where that environment variable itself comes from is the real question, and a secrets manager or a mode-600 file readable only by the service account are the usual answers.
QGIS also honours the QGIS_AUTH_PASSWORD_FILE environment variable, pointing at a file containing the password, which some deployments prefer because a file has permissions and an environment variable is visible in the process table.
Create a basic authentication config
A configuration is a named bundle of credentials with a generated id.
from qgis.core import QgsAuthMethodConfig
config = QgsAuthMethodConfig()
config.setName("survey_db reader")
config.setMethod("Basic")
config.setConfig("username", "gis_reader")
config.setConfig("password", os.environ["SURVEY_DB_PASSWORD"])
if not manager.storeAuthenticationConfig(config):
raise RuntimeError("failed to store the authentication config")
print("id:", config.id())
Breakdown: storeAuthenticationConfig() populates config.id() with a seven-character identifier — it is generated, not chosen, so the id must be read back after storing rather than assumed. The Basic method covers username and password for PostGIS, WMS, WFS and most HTTP services; other methods include ESRI-Token, OAuth2 and PKI-Paths for client certificates. Storing returns a boolean rather than raising, and a False almost always means the master password is not set.
To reuse an existing config rather than creating duplicates on every run:
def config_id_by_name(manager, name):
for cfg_id, cfg in manager.availableAuthMethodConfigs().items():
if cfg.name() == name:
return cfg_id
return None
Breakdown: Names are not unique and ids are not memorable, so a lookup by name is what makes a script idempotent. availableAuthMethodConfigs() returns configs without their secrets — the passwords are only loaded by loadAuthenticationConfig() with full=True — which is a sensible default and occasionally surprising when a config looks empty.
Use it in a data source
The id goes into the URI as authcfg, and everything else about the connection stays the same.
from qgis.core import QgsDataSourceUri, QgsVectorLayer, QgsProject
uri = QgsDataSourceUri()
uri.setConnection("db.example.org", "5432", "survey", "", "")
uri.setAuthConfigId(config.id())
uri.setDataSource("public", "parcels", "geom", "", "gid")
layer = QgsVectorLayer(uri.uri(False), "parcels", "postgres")
if not layer.isValid():
raise RuntimeError("layer failed to load — check the auth config and network")
QgsProject.instance().addMapLayer(layer)
Breakdown: The username and password arguments to setConnection() are left empty because the auth config supplies them; passing both is not an error but the auth config wins, which makes debugging confusing. uri.uri(False) omits credentials from the string — with True it would expand them, which defeats the purpose. The same authcfg parameter works in the URI for WMS, WFS, XYZ and vector tile sources, so one configuration can serve several layers.
Unattended runs
The awkward truth is that the auth database moves the secret rather than removing it: something must supply the master password, and that something is now the thing to protect.
Three arrangements are common. A service account with a mode-600 password file pointed at by QGIS_AUTH_PASSWORD_FILE is the simplest and relies on filesystem permissions. A secrets manager injecting the value into the environment at start-up is better where one exists, because the password is never at rest on the machine. And for a container, an image with no credentials and a mounted secret keeps the two lifecycles separate.
import os
from pathlib import Path
def master_password():
if "QGIS_AUTH_PASSWORD" in os.environ:
return os.environ["QGIS_AUTH_PASSWORD"]
path = os.environ.get("QGIS_AUTH_PASSWORD_FILE")
if path and Path(path).exists():
return Path(path).read_text().strip()
raise RuntimeError(
"no master password available; set QGIS_AUTH_PASSWORD or QGIS_AUTH_PASSWORD_FILE"
)
Breakdown: Preferring the environment variable and falling back to a file lets one script work in a container and on a scheduled host without a code change. .strip() on the file contents matters more than it looks: a trailing newline from an editor produces a password that does not match and an error that says nothing useful. Raising with the names of both variables turns a deployment mistake into a self-explaining failure — worth doing on every environment lookup in an unattended script.
Moving the store between machines
The auth database is portable, and a deployment usually needs it to be.
Copying qgis-auth.db from the profile directory to another machine works, provided the same master password is used there. The database is encrypted with a key derived from that password, so the file alone is not usable — which is exactly the property that makes it safe to put in a configuration management system while the password comes from somewhere else. What does not work is copying it and expecting a different master password to open it; there is no re-key operation short of exporting the configs and recreating them.
For a fleet, the pragmatic arrangement is to generate the database once, store it as a deployment artefact, and distribute the master password through whatever channel already handles secrets. Each machine then has identical config ids, which means the same project file works everywhere.
QGIS version compatibility
The examples target QGIS 3.34 LTR (Python 3.12).
| QGIS version | Python | Notes |
|---|---|---|
| 3.16 LTR | 3.7 | QgsAuthManager, Basic and PKI methods; authcfg in provider URIs. |
| 3.22 LTR | 3.9 | OAuth2 method improvements; QGIS_AUTH_PASSWORD_FILE honoured. |
| 3.28 LTR | 3.9 | Auth database schema stable; configs portable between these versions. |
| 3.34 LTR | 3.12 | Baseline for this page. |
| 3.40+ | 3.12 | Additional auth methods and improved handling of expired tokens. |
Troubleshooting
storeAuthenticationConfigreturns False. The master password is not set. CheckmasterPasswordIsSet().- The password is rejected on a copied database. A different master password was used. The file cannot be re-keyed.
- A layer loads interactively and not from cron. No master password was available headlessly. Set the environment variable or password file.
- The password file does not work. It has a trailing newline. Strip it.
- The config id is empty. It is generated on store; read
config.id()afterstoreAuthenticationConfig(), not before. - Credentials appear in the project file anyway. The URI was built with
uri(True), or the username and password were also passed tosetConnection().
Conclusion
Set the master password from the environment or a permissioned file, create configurations once and look them up by name so scripts stay idempotent, and reference them from URIs with authcfg rather than embedding credentials. Understand that this relocates the secret rather than eliminating it, and protect the master password accordingly.
Frequently Asked Questions
Where is the auth database?qgis-auth.db inside the active user profile directory, reported by QgsApplication.qgisSettingsDirPath(). It is SQLite, but the credential fields are encrypted.
Can I use it for an API key in a URL?
Yes — the Basic method can supply it, or ESRI-Token for token headers. For a key that must appear as a query parameter, the auth manager can rewrite the request, which keeps the key out of the stored URI.
Does the master password prompt appear in a plugin? Yes, on first use in a session, unless it was already set. A plugin that needs credentials early should trigger the prompt deliberately rather than letting it appear halfway through an operation.
Is this suitable for sharing credentials with a team? It is suitable for distributing a database plus a separately managed password. It is not a substitute for per-user accounts — a shared read-only role is fine, a shared write role removes any audit trail.