[{"data":1,"prerenderedAt":1314},["ShallowReactive",2],{"doc:\u002Fpyqgis-fundamentals-environment-setup\u002Fheadless-qgis-and-server-automation\u002Fstore-credentials-with-qgsauthmanager-pyqgis":3},{"id":4,"title":5,"body":6,"description":1303,"extension":1304,"meta":1305,"navigation":254,"path":1310,"seo":1311,"stem":1312,"__hash__":1313},"docs\u002Fpyqgis-fundamentals-environment-setup\u002Fheadless-qgis-and-server-automation\u002Fstore-credentials-with-qgsauthmanager-pyqgis\u002Findex.md","Store Credentials with QgsAuthManager in PyQGIS",{"type":7,"value":8,"toc":1290},"minimark",[9,13,17,26,181,186,207,211,214,328,343,350,354,357,486,517,520,581,598,602,609,757,781,871,875,878,896,1014,1027,1031,1034,1044,1047,1051,1057,1142,1146,1202,1206,1212,1216,1228,1240,1246,1252,1256,1286],[10,11,5],"h1",{"id":12},"store-credentials-with-qgsauthmanager-in-pyqgis",[14,15,16],"p",{},"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.",[14,18,19,20,25],{},"This recipe belongs to ",[21,22,24],"a",{"href":23},"\u002Fpyqgis-fundamentals-environment-setup\u002Fheadless-qgis-and-server-automation\u002F","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.",[14,27,28],{},[29,30,35,39,43,50,67,76,86,92,97,102,107,114,118,122,125,128,131,135,141,147,151,156,164,169,173,177],"svg",{"viewBox":31,"role":32,"ariaLabel":33,"xmlns":34},"0 0 760 316","img","A project file referencing an authentication configuration by id, with the credentials themselves living in a separate encrypted database that never leaves the machine","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[36,37,38],"title",{},"The project holds a reference, not a secret",[40,41,42],"desc",{},"A layer's data source URI carries an authentication configuration id rather than a username and password. The credentials live in an encrypted authentication database in the user profile, unlocked by a master password. Sharing the project shares only the id, which is useless without the database.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","316","#f6f3ea",[51,52,53],"defs",{},[54,55,62],"marker",{"id":56,"viewBox":57,"refX":58,"refY":59,"markerWidth":60,"markerHeight":60,"orient":61},"authArrow","0 0 10 10","8","5","7","auto-start-reverse",[63,64],"path",{"d":65,"fill":66},"M0 0 L10 5 L0 10 z","#2f3b35",[68,69,75],"text",{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"380","28","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","Share the project freely; the secret stays behind",[44,77],{"x":78,"y":79,"width":80,"height":81,"rx":82,"fill":83,"stroke":84,"style":85},"24","60","250","106","10","#eff3ff","#2563eb","stroke-width:2.5",[68,87,91],{"x":88,"y":89,"style":90,"fill":84,"textAnchor":74},"149","88","text-anchor:middle;font-size:11px;font-weight:bold;font-family:sans-serif","the project file",[68,93,96],{"x":88,"y":94,"style":95,"fill":66,"textAnchor":74},"114","text-anchor:middle;font-size:9.5px;font-family:monospace","dbname=survey host=db…",[68,98,101],{"x":88,"y":99,"style":95,"fill":100,"textAnchor":74},"134","#15803d","authcfg=abc1234",[68,103,106],{"x":88,"y":104,"style":105,"fill":66,"textAnchor":74},"156","text-anchor:middle;font-size:10px;font-family:sans-serif","no username, no password",[108,109],"line",{"x1":110,"y1":111,"x2":112,"y2":111,"stroke":66,"style":113},"280","112","322","stroke-width:2;marker-end:url(#authArrow)",[44,115],{"x":116,"y":79,"width":80,"height":81,"rx":82,"fill":117,"stroke":100,"style":85},"330","#edf8e9",[68,119,121],{"x":120,"y":89,"style":90,"fill":100,"textAnchor":74},"455","qgis-auth.db",[68,123,124],{"x":120,"y":111,"style":105,"fill":66,"textAnchor":74},"encrypted, in the profile",[68,126,127],{"x":120,"y":99,"style":105,"fill":66,"textAnchor":74},"abc1234 → user + password",[68,129,130],{"x":120,"y":104,"style":105,"fill":66,"textAnchor":74},"never in version control",[108,132],{"x1":133,"y1":111,"x2":134,"y2":111,"stroke":66,"style":113},"586","628",[44,136],{"x":137,"y":79,"width":138,"height":81,"rx":82,"fill":139,"stroke":140,"style":85},"636","100","#fdf2e2","#b45309",[68,142,146],{"x":143,"y":144,"style":145,"fill":140,"textAnchor":74},"686","98","text-anchor:middle;font-size:10.5px;font-weight:bold;font-family:sans-serif","master",[68,148,150],{"x":143,"y":149,"style":145,"fill":140,"textAnchor":74},"116","password",[68,152,155],{"x":143,"y":153,"style":154,"fill":66,"textAnchor":74},"140","text-anchor:middle;font-size:9.5px;font-family:sans-serif","unlocks it",[44,157],{"x":158,"y":159,"width":160,"height":161,"rx":82,"fill":162,"stroke":163,"style":85},"120","200","520","94","#fffdf7","#b91c1c",[68,165,168],{"x":70,"y":166,"style":167,"fill":163,"textAnchor":74},"226","text-anchor:middle;font-size:11.5px;font-weight:bold;font-family:sans-serif","the unattended problem",[68,170,172],{"x":70,"y":80,"style":171,"fill":66,"textAnchor":74},"text-anchor:middle;font-size:10.5px;font-family:sans-serif","a cron job cannot type the master password —",[68,174,176],{"x":70,"y":175,"style":171,"fill":66,"textAnchor":74},"270","it must come from the environment or a secrets store,",[68,178,180],{"x":70,"y":179,"style":171,"fill":66,"textAnchor":74},"288","which moves the problem rather than removing it",[182,183,185],"h2",{"id":184},"prerequisites","Prerequisites",[187,188,189,197,204],"ul",{},[190,191,192,196],"li",{},[193,194,195],"strong",{},"QGIS 3.34 LTR"," (bundled Python 3.12) or newer built with authentication support, which every standard build is.",[190,198,199,200,203],{},"A writable user profile directory. The auth database lives there as ",[201,202,121],"code",{},".",[190,205,206],{},"A decision about where the master password will come from in unattended use, before you start.",[182,208,210],{"id":209},"initialise-and-unlock","Initialise and unlock",[14,212,213],{},"Nothing works until the auth manager is initialised and the master password is set.",[215,216,221],"pre",{"className":217,"code":218,"language":219,"meta":220,"style":220},"language-python shiki shiki-themes github-dark","import os\nfrom qgis.core import QgsApplication\n\nmanager = QgsApplication.authManager()\nmanager.setMasterPassword(os.environ[\"QGIS_AUTH_PASSWORD\"], verify=True)\n\nif not manager.masterPasswordIsSet():\n    raise RuntimeError(\"master password was not accepted\")\n","python","",[201,222,223,235,249,256,268,294,299,311],{"__ignoreMap":220},[224,225,227,231],"span",{"class":108,"line":226},1,[224,228,230],{"class":229},"snl16","import",[224,232,234],{"class":233},"s95oV"," os\n",[224,236,238,241,244,246],{"class":108,"line":237},2,[224,239,240],{"class":229},"from",[224,242,243],{"class":233}," qgis.core ",[224,245,230],{"class":229},[224,247,248],{"class":233}," QgsApplication\n",[224,250,252],{"class":108,"line":251},3,[224,253,255],{"emptyLinePlaceholder":254},true,"\n",[224,257,259,262,265],{"class":108,"line":258},4,[224,260,261],{"class":233},"manager ",[224,263,264],{"class":229},"=",[224,266,267],{"class":233}," QgsApplication.authManager()\n",[224,269,271,274,278,281,285,287,291],{"class":108,"line":270},5,[224,272,273],{"class":233},"manager.setMasterPassword(os.environ[",[224,275,277],{"class":276},"sU2Wk","\"QGIS_AUTH_PASSWORD\"",[224,279,280],{"class":233},"], ",[224,282,284],{"class":283},"s9osk","verify",[224,286,264],{"class":229},[224,288,290],{"class":289},"sDLfK","True",[224,292,293],{"class":233},")\n",[224,295,297],{"class":108,"line":296},6,[224,298,255],{"emptyLinePlaceholder":254},[224,300,302,305,308],{"class":108,"line":301},7,[224,303,304],{"class":229},"if",[224,306,307],{"class":229}," not",[224,309,310],{"class":233}," manager.masterPasswordIsSet():\n",[224,312,314,317,320,323,326],{"class":108,"line":313},8,[224,315,316],{"class":229},"    raise",[224,318,319],{"class":289}," RuntimeError",[224,321,322],{"class":233},"(",[224,324,325],{"class":276},"\"master password was not accepted\"",[224,327,293],{"class":233},[14,329,330,333,334,337,338,342],{},[193,331,332],{},"Breakdown:"," ",[201,335,336],{},"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 ",[339,340,341],"em",{},"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.",[14,344,345,346,349],{},"QGIS also honours the ",[201,347,348],{},"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.",[182,351,353],{"id":352},"create-a-basic-authentication-config","Create a basic authentication config",[14,355,356],{},"A configuration is a named bundle of credentials with a generated id.",[215,358,360],{"className":217,"code":359,"language":219,"meta":220,"style":220},"from qgis.core import QgsAuthMethodConfig\n\nconfig = QgsAuthMethodConfig()\nconfig.setName(\"survey_db reader\")\nconfig.setMethod(\"Basic\")\nconfig.setConfig(\"username\", \"gis_reader\")\nconfig.setConfig(\"password\", os.environ[\"SURVEY_DB_PASSWORD\"])\n\nif not manager.storeAuthenticationConfig(config):\n    raise RuntimeError(\"failed to store the authentication config\")\n\nprint(\"id:\", config.id())\n",[201,361,362,373,377,387,397,407,423,439,443,453,467,472],{"__ignoreMap":220},[224,363,364,366,368,370],{"class":108,"line":226},[224,365,240],{"class":229},[224,367,243],{"class":233},[224,369,230],{"class":229},[224,371,372],{"class":233}," QgsAuthMethodConfig\n",[224,374,375],{"class":108,"line":237},[224,376,255],{"emptyLinePlaceholder":254},[224,378,379,382,384],{"class":108,"line":251},[224,380,381],{"class":233},"config ",[224,383,264],{"class":229},[224,385,386],{"class":233}," QgsAuthMethodConfig()\n",[224,388,389,392,395],{"class":108,"line":258},[224,390,391],{"class":233},"config.setName(",[224,393,394],{"class":276},"\"survey_db reader\"",[224,396,293],{"class":233},[224,398,399,402,405],{"class":108,"line":270},[224,400,401],{"class":233},"config.setMethod(",[224,403,404],{"class":276},"\"Basic\"",[224,406,293],{"class":233},[224,408,409,412,415,418,421],{"class":108,"line":296},[224,410,411],{"class":233},"config.setConfig(",[224,413,414],{"class":276},"\"username\"",[224,416,417],{"class":233},", ",[224,419,420],{"class":276},"\"gis_reader\"",[224,422,293],{"class":233},[224,424,425,427,430,433,436],{"class":108,"line":301},[224,426,411],{"class":233},[224,428,429],{"class":276},"\"password\"",[224,431,432],{"class":233},", os.environ[",[224,434,435],{"class":276},"\"SURVEY_DB_PASSWORD\"",[224,437,438],{"class":233},"])\n",[224,440,441],{"class":108,"line":313},[224,442,255],{"emptyLinePlaceholder":254},[224,444,446,448,450],{"class":108,"line":445},9,[224,447,304],{"class":229},[224,449,307],{"class":229},[224,451,452],{"class":233}," manager.storeAuthenticationConfig(config):\n",[224,454,456,458,460,462,465],{"class":108,"line":455},10,[224,457,316],{"class":229},[224,459,319],{"class":289},[224,461,322],{"class":233},[224,463,464],{"class":276},"\"failed to store the authentication config\"",[224,466,293],{"class":233},[224,468,470],{"class":108,"line":469},11,[224,471,255],{"emptyLinePlaceholder":254},[224,473,475,478,480,483],{"class":108,"line":474},12,[224,476,477],{"class":289},"print",[224,479,322],{"class":233},[224,481,482],{"class":276},"\"id:\"",[224,484,485],{"class":233},", config.id())\n",[14,487,488,333,490,493,494,497,498,501,502,417,505,508,509,512,513,516],{},[193,489,332],{},[201,491,492],{},"storeAuthenticationConfig()"," populates ",[201,495,496],{},"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 ",[201,499,500],{},"Basic"," method covers username and password for PostGIS, WMS, WFS and most HTTP services; other methods include ",[201,503,504],{},"ESRI-Token",[201,506,507],{},"OAuth2"," and ",[201,510,511],{},"PKI-Paths"," for client certificates. Storing returns a boolean rather than raising, and a ",[201,514,515],{},"False"," almost always means the master password is not set.",[14,518,519],{},"To reuse an existing config rather than creating duplicates on every run:",[215,521,523],{"className":217,"code":522,"language":219,"meta":220,"style":220},"def config_id_by_name(manager, name):\n    for cfg_id, cfg in manager.availableAuthMethodConfigs().items():\n        if cfg.name() == name:\n            return cfg_id\n    return None\n",[201,524,525,537,551,565,573],{"__ignoreMap":220},[224,526,527,530,534],{"class":108,"line":226},[224,528,529],{"class":229},"def",[224,531,533],{"class":532},"svObZ"," config_id_by_name",[224,535,536],{"class":233},"(manager, name):\n",[224,538,539,542,545,548],{"class":108,"line":237},[224,540,541],{"class":229},"    for",[224,543,544],{"class":233}," cfg_id, cfg ",[224,546,547],{"class":229},"in",[224,549,550],{"class":233}," manager.availableAuthMethodConfigs().items():\n",[224,552,553,556,559,562],{"class":108,"line":251},[224,554,555],{"class":229},"        if",[224,557,558],{"class":233}," cfg.name() ",[224,560,561],{"class":229},"==",[224,563,564],{"class":233}," name:\n",[224,566,567,570],{"class":108,"line":258},[224,568,569],{"class":229},"            return",[224,571,572],{"class":233}," cfg_id\n",[224,574,575,578],{"class":108,"line":270},[224,576,577],{"class":229},"    return",[224,579,580],{"class":289}," None\n",[14,582,583,585,586,589,590,593,594,597],{},[193,584,332],{}," Names are not unique and ids are not memorable, so a lookup by name is what makes a script idempotent. ",[201,587,588],{},"availableAuthMethodConfigs()"," returns configs without their secrets — the passwords are only loaded by ",[201,591,592],{},"loadAuthenticationConfig()"," with ",[201,595,596],{},"full=True"," — which is a sensible default and occasionally surprising when a config looks empty.",[182,599,601],{"id":600},"use-it-in-a-data-source","Use it in a data source",[14,603,604,605,608],{},"The id goes into the URI as ",[201,606,607],{},"authcfg",", and everything else about the connection stays the same.",[215,610,612],{"className":217,"code":611,"language":219,"meta":220,"style":220},"from qgis.core import QgsDataSourceUri, QgsVectorLayer, QgsProject\n\nuri = QgsDataSourceUri()\nuri.setConnection(\"db.example.org\", \"5432\", \"survey\", \"\", \"\")\nuri.setAuthConfigId(config.id())\nuri.setDataSource(\"public\", \"parcels\", \"geom\", \"\", \"gid\")\n\nlayer = QgsVectorLayer(uri.uri(False), \"parcels\", \"postgres\")\nif not layer.isValid():\n    raise RuntimeError(\"layer failed to load — check the auth config and network\")\nQgsProject.instance().addMapLayer(layer)\n",[201,613,614,625,629,639,668,673,702,706,730,739,752],{"__ignoreMap":220},[224,615,616,618,620,622],{"class":108,"line":226},[224,617,240],{"class":229},[224,619,243],{"class":233},[224,621,230],{"class":229},[224,623,624],{"class":233}," QgsDataSourceUri, QgsVectorLayer, QgsProject\n",[224,626,627],{"class":108,"line":237},[224,628,255],{"emptyLinePlaceholder":254},[224,630,631,634,636],{"class":108,"line":251},[224,632,633],{"class":233},"uri ",[224,635,264],{"class":229},[224,637,638],{"class":233}," QgsDataSourceUri()\n",[224,640,641,644,647,649,652,654,657,659,662,664,666],{"class":108,"line":258},[224,642,643],{"class":233},"uri.setConnection(",[224,645,646],{"class":276},"\"db.example.org\"",[224,648,417],{"class":233},[224,650,651],{"class":276},"\"5432\"",[224,653,417],{"class":233},[224,655,656],{"class":276},"\"survey\"",[224,658,417],{"class":233},[224,660,661],{"class":276},"\"\"",[224,663,417],{"class":233},[224,665,661],{"class":276},[224,667,293],{"class":233},[224,669,670],{"class":108,"line":270},[224,671,672],{"class":233},"uri.setAuthConfigId(config.id())\n",[224,674,675,678,681,683,686,688,691,693,695,697,700],{"class":108,"line":296},[224,676,677],{"class":233},"uri.setDataSource(",[224,679,680],{"class":276},"\"public\"",[224,682,417],{"class":233},[224,684,685],{"class":276},"\"parcels\"",[224,687,417],{"class":233},[224,689,690],{"class":276},"\"geom\"",[224,692,417],{"class":233},[224,694,661],{"class":276},[224,696,417],{"class":233},[224,698,699],{"class":276},"\"gid\"",[224,701,293],{"class":233},[224,703,704],{"class":108,"line":301},[224,705,255],{"emptyLinePlaceholder":254},[224,707,708,711,713,716,718,721,723,725,728],{"class":108,"line":313},[224,709,710],{"class":233},"layer ",[224,712,264],{"class":229},[224,714,715],{"class":233}," QgsVectorLayer(uri.uri(",[224,717,515],{"class":289},[224,719,720],{"class":233},"), ",[224,722,685],{"class":276},[224,724,417],{"class":233},[224,726,727],{"class":276},"\"postgres\"",[224,729,293],{"class":233},[224,731,732,734,736],{"class":108,"line":445},[224,733,304],{"class":229},[224,735,307],{"class":229},[224,737,738],{"class":233}," layer.isValid():\n",[224,740,741,743,745,747,750],{"class":108,"line":455},[224,742,316],{"class":229},[224,744,319],{"class":289},[224,746,322],{"class":233},[224,748,749],{"class":276},"\"layer failed to load — check the auth config and network\"",[224,751,293],{"class":233},[224,753,754],{"class":108,"line":469},[224,755,756],{"class":233},"QgsProject.instance().addMapLayer(layer)\n",[14,758,759,761,762,765,766,769,770,772,773,775,776,780],{},[193,760,332],{}," The username and password arguments to ",[201,763,764],{},"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. ",[201,767,768],{},"uri.uri(False)"," omits credentials from the string — with ",[201,771,290],{}," it would expand them, which defeats the purpose. The same ",[201,774,607],{}," parameter works in the URI for WMS, WFS, XYZ and ",[21,777,779],{"href":778},"\u002Fspatial-data-processing-automation\u002Fweb-services-and-remote-data\u002Fload-vector-tile-layer-pyqgis\u002F","vector tile"," sources, so one configuration can serve several layers.",[14,782,783],{},[29,784,787,790,793,795,802,805,808,813,817,821,828,831,834,837,845,850,853,857,860,864,867],{"viewBox":785,"role":32,"ariaLabel":786,"xmlns":34},"0 0 760 288","One authentication configuration referenced by several different layer types, each supplying the same credentials to a different provider",[36,788,789],{},"One configuration, many providers",[40,791,792],{},"A single stored configuration holding a username and password is referenced by its id from a PostGIS layer, a WFS layer, a WMS layer and a vector tile source. Each provider reads the credentials from the auth manager at connection time, so a password change is one edit rather than four.",[44,794],{"x":46,"y":46,"width":47,"height":179,"fill":49},[51,796,797],{},[54,798,800],{"id":799,"viewBox":57,"refX":58,"refY":59,"markerWidth":60,"markerHeight":60,"orient":61},"cfgArrow",[63,801],{"d":65,"fill":66},[68,803,804],{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"Change the password once, not in four places",[44,806],{"x":78,"y":81,"width":159,"height":807,"rx":82,"fill":117,"stroke":100,"style":85},"86",[68,809,101],{"x":810,"y":811,"style":812,"fill":100,"textAnchor":74},"124","136","text-anchor:middle;font-size:11px;font-weight:bold;font-family:monospace",[68,814,816],{"x":810,"y":815,"style":105,"fill":66,"textAnchor":74},"160","Basic · gis_reader",[68,818,820],{"x":810,"y":819,"style":105,"fill":66,"textAnchor":74},"180","one stored config",[108,822],{"x1":823,"y1":824,"x2":825,"y2":826,"stroke":66,"style":827},"230","130","290","82","stroke-width:2;marker-end:url(#cfgArrow)",[108,829],{"x1":823,"y1":830,"x2":825,"y2":824,"stroke":66,"style":827},"143",[108,832],{"x1":823,"y1":104,"x2":825,"y2":833,"stroke":66,"style":827},"178",[108,835],{"x1":823,"y1":836,"x2":825,"y2":166,"stroke":66,"style":827},"169",[44,838],{"x":839,"y":840,"width":841,"height":842,"rx":843,"fill":83,"stroke":84,"style":844},"298","56","438","46","6","stroke-width:1.8",[68,846,849],{"x":847,"y":848,"style":105,"fill":66,"textAnchor":74},"517","84","PostGIS layer — parcels, buildings, roads",[44,851],{"x":839,"y":852,"width":841,"height":842,"rx":843,"fill":83,"stroke":84,"style":844},"110",[68,854,856],{"x":847,"y":855,"style":105,"fill":66,"textAnchor":74},"138","WFS layer — the same server's published features",[44,858],{"x":839,"y":859,"width":841,"height":842,"rx":843,"fill":83,"stroke":84,"style":844},"164",[68,861,863],{"x":847,"y":862,"style":105,"fill":66,"textAnchor":74},"192","WMS layer — the rendered basemap",[44,865],{"x":839,"y":866,"width":841,"height":842,"rx":843,"fill":83,"stroke":84,"style":844},"218",[68,868,870],{"x":847,"y":869,"style":105,"fill":66,"textAnchor":74},"246","vector tiles — the styled base layer",[182,872,874],{"id":873},"unattended-runs","Unattended runs",[14,876,877],{},"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.",[14,879,880,881,884,885,887,888,891,892,895],{},"Three arrangements are common. A ",[193,882,883],{},"service account with a mode-600 password file"," pointed at by ",[201,886,348],{}," is the simplest and relies on filesystem permissions. A ",[193,889,890],{},"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 ",[193,893,894],{},"image with no credentials and a mounted secret"," keeps the two lifecycles separate.",[215,897,899],{"className":217,"code":898,"language":219,"meta":220,"style":220},"import os\nfrom pathlib import Path\n\ndef master_password():\n    if \"QGIS_AUTH_PASSWORD\" in os.environ:\n        return os.environ[\"QGIS_AUTH_PASSWORD\"]\n    path = os.environ.get(\"QGIS_AUTH_PASSWORD_FILE\")\n    if path and Path(path).exists():\n        return Path(path).read_text().strip()\n    raise RuntimeError(\n        \"no master password available; set QGIS_AUTH_PASSWORD or QGIS_AUTH_PASSWORD_FILE\"\n    )\n",[201,900,901,907,919,923,933,947,960,975,988,995,1004,1009],{"__ignoreMap":220},[224,902,903,905],{"class":108,"line":226},[224,904,230],{"class":229},[224,906,234],{"class":233},[224,908,909,911,914,916],{"class":108,"line":237},[224,910,240],{"class":229},[224,912,913],{"class":233}," pathlib ",[224,915,230],{"class":229},[224,917,918],{"class":233}," Path\n",[224,920,921],{"class":108,"line":251},[224,922,255],{"emptyLinePlaceholder":254},[224,924,925,927,930],{"class":108,"line":258},[224,926,529],{"class":229},[224,928,929],{"class":532}," master_password",[224,931,932],{"class":233},"():\n",[224,934,935,938,941,944],{"class":108,"line":270},[224,936,937],{"class":229},"    if",[224,939,940],{"class":276}," \"QGIS_AUTH_PASSWORD\"",[224,942,943],{"class":229}," in",[224,945,946],{"class":233}," os.environ:\n",[224,948,949,952,955,957],{"class":108,"line":296},[224,950,951],{"class":229},"        return",[224,953,954],{"class":233}," os.environ[",[224,956,277],{"class":276},[224,958,959],{"class":233},"]\n",[224,961,962,965,967,970,973],{"class":108,"line":301},[224,963,964],{"class":233},"    path ",[224,966,264],{"class":229},[224,968,969],{"class":233}," os.environ.get(",[224,971,972],{"class":276},"\"QGIS_AUTH_PASSWORD_FILE\"",[224,974,293],{"class":233},[224,976,977,979,982,985],{"class":108,"line":313},[224,978,937],{"class":229},[224,980,981],{"class":233}," path ",[224,983,984],{"class":229},"and",[224,986,987],{"class":233}," Path(path).exists():\n",[224,989,990,992],{"class":108,"line":445},[224,991,951],{"class":229},[224,993,994],{"class":233}," Path(path).read_text().strip()\n",[224,996,997,999,1001],{"class":108,"line":455},[224,998,316],{"class":229},[224,1000,319],{"class":289},[224,1002,1003],{"class":233},"(\n",[224,1005,1006],{"class":108,"line":469},[224,1007,1008],{"class":276},"        \"no master password available; set QGIS_AUTH_PASSWORD or QGIS_AUTH_PASSWORD_FILE\"\n",[224,1010,1011],{"class":108,"line":474},[224,1012,1013],{"class":233},"    )\n",[14,1015,1016,1018,1019,1022,1023,203],{},[193,1017,332],{}," 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. ",[201,1020,1021],{},".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 ",[21,1024,1026],{"href":1025},"\u002Fpyqgis-fundamentals-environment-setup\u002Fheadless-qgis-and-server-automation\u002Fhandle-errors-and-logging-in-unattended-scripts\u002F","unattended script",[182,1028,1030],{"id":1029},"moving-the-store-between-machines","Moving the store between machines",[14,1032,1033],{},"The auth database is portable, and a deployment usually needs it to be.",[14,1035,1036,1037,1039,1040,1043],{},"Copying ",[201,1038,121],{}," 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 ",[339,1041,1042],{},"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.",[14,1045,1046],{},"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.",[182,1048,1050],{"id":1049},"qgis-version-compatibility","QGIS version compatibility",[14,1052,1053,1054,1056],{},"The examples target ",[193,1055,195],{}," (Python 3.12).",[1058,1059,1060,1076],"table",{},[1061,1062,1063],"thead",{},[1064,1065,1066,1070,1073],"tr",{},[1067,1068,1069],"th",{},"QGIS version",[1067,1071,1072],{},"Python",[1067,1074,1075],{},"Notes",[1077,1078,1079,1097,1111,1121,1132],"tbody",{},[1064,1080,1081,1085,1088],{},[1082,1083,1084],"td",{},"3.16 LTR",[1082,1086,1087],{},"3.7",[1082,1089,1090,1093,1094,1096],{},[201,1091,1092],{},"QgsAuthManager",", Basic and PKI methods; ",[201,1095,607],{}," in provider URIs.",[1064,1098,1099,1102,1105],{},[1082,1100,1101],{},"3.22 LTR",[1082,1103,1104],{},"3.9",[1082,1106,1107,1108,1110],{},"OAuth2 method improvements; ",[201,1109,348],{}," honoured.",[1064,1112,1113,1116,1118],{},[1082,1114,1115],{},"3.28 LTR",[1082,1117,1104],{},[1082,1119,1120],{},"Auth database schema stable; configs portable between these versions.",[1064,1122,1123,1126,1129],{},[1082,1124,1125],{},"3.34 LTR",[1082,1127,1128],{},"3.12",[1082,1130,1131],{},"Baseline for this page.",[1064,1133,1134,1137,1139],{},[1082,1135,1136],{},"3.40+",[1082,1138,1128],{},[1082,1140,1141],{},"Additional auth methods and improved handling of expired tokens.",[182,1143,1145],{"id":1144},"troubleshooting","Troubleshooting",[187,1147,1148,1160,1166,1172,1178,1190],{},[190,1149,1150,1156,1157,203],{},[193,1151,1152,1155],{},[201,1153,1154],{},"storeAuthenticationConfig"," returns False."," The master password is not set. Check ",[201,1158,1159],{},"masterPasswordIsSet()",[190,1161,1162,1165],{},[193,1163,1164],{},"The password is rejected on a copied database."," A different master password was used. The file cannot be re-keyed.",[190,1167,1168,1171],{},[193,1169,1170],{},"A layer loads interactively and not from cron."," No master password was available headlessly. Set the environment variable or password file.",[190,1173,1174,1177],{},[193,1175,1176],{},"The password file does not work."," It has a trailing newline. Strip it.",[190,1179,1180,1183,1184,1186,1187,1189],{},[193,1181,1182],{},"The config id is empty."," It is generated on store; read ",[201,1185,496],{}," after ",[201,1188,492],{},", not before.",[190,1191,1192,1195,1196,1199,1200,203],{},[193,1193,1194],{},"Credentials appear in the project file anyway."," The URI was built with ",[201,1197,1198],{},"uri(True)",", or the username and password were also passed to ",[201,1201,764],{},[182,1203,1205],{"id":1204},"conclusion","Conclusion",[14,1207,1208,1209,1211],{},"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 ",[201,1210,607],{}," rather than embedding credentials. Understand that this relocates the secret rather than eliminating it, and protect the master password accordingly.",[182,1213,1215],{"id":1214},"frequently-asked-questions","Frequently Asked Questions",[14,1217,1218,1221,1223,1224,1227],{},[193,1219,1220],{},"Where is the auth database?",[201,1222,121],{}," inside the active user profile directory, reported by ",[201,1225,1226],{},"QgsApplication.qgisSettingsDirPath()",". It is SQLite, but the credential fields are encrypted.",[14,1229,1230,1233,1234,1236,1237,1239],{},[193,1231,1232],{},"Can I use it for an API key in a URL?","\nYes — the ",[201,1235,500],{}," method can supply it, or ",[201,1238,504],{}," 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.",[14,1241,1242,1245],{},[193,1243,1244],{},"Does the master password prompt appear in a plugin?","\nYes, 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.",[14,1247,1248,1251],{},[193,1249,1250],{},"Is this suitable for sharing credentials with a team?","\nIt 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.",[182,1253,1255],{"id":1254},"related","Related",[187,1257,1258,1263,1268,1274,1280],{},[190,1259,1260,1262],{},[21,1261,24],{"href":23}," — the guide this recipe belongs to",[190,1264,1265],{},[21,1266,1267],{"href":1025},"Handle Errors and Logging in Unattended Scripts",[190,1269,1270],{},[21,1271,1273],{"href":1272},"\u002Fpyqgis-fundamentals-environment-setup\u002Fheadless-qgis-and-server-automation\u002Fschedule-pyqgis-scripts-with-cron\u002F","Schedule PyQGIS Scripts with cron",[190,1275,1276],{},[21,1277,1279],{"href":1278},"\u002Fspatial-data-processing-automation\u002Fpostgis-and-database-workflows\u002Fconnect-to-postgis-database-pyqgis\u002F","Connect to a PostGIS Database in PyQGIS",[190,1281,1282],{},[21,1283,1285],{"href":1284},"\u002Fspatial-data-processing-automation\u002Fweb-services-and-remote-data\u002Fload-wms-layer-pyqgis\u002F","Load a WMS Layer in PyQGIS",[1287,1288,1289],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}",{"title":220,"searchDepth":237,"depth":237,"links":1291},[1292,1293,1294,1295,1296,1297,1298,1299,1300,1301,1302],{"id":184,"depth":237,"text":185},{"id":209,"depth":237,"text":210},{"id":352,"depth":237,"text":353},{"id":600,"depth":237,"text":601},{"id":873,"depth":237,"text":874},{"id":1029,"depth":237,"text":1030},{"id":1049,"depth":237,"text":1050},{"id":1144,"depth":237,"text":1145},{"id":1204,"depth":237,"text":1205},{"id":1214,"depth":237,"text":1215},{"id":1254,"depth":237,"text":1255},"Keep passwords and tokens out of project files using the QGIS authentication database — setting the master password non-interactively, creating configs, and referencing them from layer URIs.","md",{"slug":1306,"type":1307,"breadcrumb":1308,"datePublished":1309,"dateModified":1309},"store-credentials-with-qgsauthmanager-pyqgis","article","Credentials & Auth Manager","2026-08-27","\u002Fpyqgis-fundamentals-environment-setup\u002Fheadless-qgis-and-server-automation\u002Fstore-credentials-with-qgsauthmanager-pyqgis",{"title":5,"description":1303},"pyqgis-fundamentals-environment-setup\u002Fheadless-qgis-and-server-automation\u002Fstore-credentials-with-qgsauthmanager-pyqgis\u002Findex","M0YY2An88vAvvljl7gyEx5k2K9JFtTEjBCYN1rsaTn0",1787823360563]