[{"data":1,"prerenderedAt":2287},["ShallowReactive",2],{"docs_single-sign-on":3,"nav-docs":2188,"nav-api":2241},{"id":4,"title":5,"body":6,"description":2181,"extension":2182,"meta":2183,"navigation":1674,"path":2184,"seo":2185,"stem":2186,"__hash__":2187},"docs\u002Fdocs\u002F08.single-sign-on.md","Single Sign-On",{"type":7,"value":8,"toc":2145},"minimark",[9,13,26,29,34,68,74,78,90,94,97,108,127,131,134,167,174,179,454,461,465,468,472,511,536,546,561,565,588,619,622,626,641,666,672,676,701,719,737,741,764,782,786,804,809,813,816,841,844,848,864,884,890,899,905,914,917,920,934,937,940,943,968,986,990,1059,1069,1073,1377,1381,1400,1425,1428,1432,1454,1466,1488,1504,1510,1514,1524,1536,1540,1543,1602,1617,1624,1628,1631,1640,1647,1653,1657,1660,1726,1736,1743,1749,1760,1764,1767,1787,1790,1800,1804,1807,1813,1819,1848,1854,1858,1872,1875,1878,1882,1885,1888,1891,1902,1906,1916,1920,1923,1968,1971,1975,1978,2125,2138,2141],[10,11,5],"h1",{"id":12},"single-sign-on",[14,15,16,17,21,22,25],"p",{},"LokalBoards signs people in against your own identity provider — Entra ID,\nGoogle Workspace, Okta, Keycloak, Authentik, Auth0, Zitadel, ADFS, Shibboleth,\nor anything else that speaks ",[18,19,20],"strong",{},"OpenID Connect"," or ",[18,23,24],{},"SAML 2.0",". One button on\nthe sign-in page, no password to keep here, and joiners and leavers handled\nwhere you already handle them.",[14,27,28],{},"It is part of the MIT licence like everything else. There is no edition to buy.",[30,31,33],"h2",{"id":32},"what-it-does","What it does",[35,36,37,44,50,56,62],"ul",{},[38,39,40,43],"li",{},[18,41,42],{},"One button, your wording."," \"Sign in with Contoso\", if that is what you call\nit.",[38,45,46,49],{},[18,47,48],{},"Accounts on first use."," Somebody signing in for the first time gets an\naccount here, named and addressed from what your provider says about them.",[38,51,52,55],{},[18,53,54],{},"Existing accounts are joined, not duplicated."," Anyone already using the\ninstance keeps their boards, their cards and their comments the first time\nthey arrive through the provider.",[38,57,58,61],{},[18,59,60],{},"Administrators from your directory."," Optional: a group or role claim\ndecides who administers this instance, checked on every sign-in.",[38,63,64,67],{},[18,65,66],{},"Passwords still work."," Adding SSO takes nothing away: the password form\nstays on the sign-in page, existing passwords keep working, and administrators\ncan still create accounts with a password from the Users page. Useful for a\nbreak-glass account, or for people your directory does not cover.",[14,69,70,73],{},[18,71,72],{},"Which protocol?"," If your provider offers both, choose OpenID Connect: less to\nconfigure, no certificates to rotate. Choose SAML when that is what your provider\nspeaks — which in practice means a good deal of higher education, public\nadministration and anything bought before about 2015. Both can be switched on at\nonce, and each gets its own button; an account is the same account either way.",[30,75,77],{"id":76},"what-you-need","What you need",[35,79,80,83],{},[38,81,82],{},"An OpenID Connect or SAML 2.0 provider you can register an application with.",[38,84,85,86,89],{},"LokalBoards reachable over ",[18,87,88],{},"HTTPS"," at a fixed address. The provider redirects\nbrowsers back to it, so it has to be an address your provider will accept.",[30,91,93],{"id":92},"the-redirect-uri","The redirect URI",[14,95,96],{},"Register exactly this address with your provider, with your own domain in front:",[98,99,104],"pre",{"className":100,"code":102,"language":103},[101],"language-text","https:\u002F\u002Fboards.example.com\u002Fapi\u002Fauth\u002Fsso\u002Fcallback\n","text",[105,106,102],"code",{"__ignoreMap":107},"",[14,109,110,111,114,115,118,119,122,123,126],{},"It has to match to the character — a trailing slash, ",[105,112,113],{},"http"," instead of ",[105,116,117],{},"https",",\nor ",[105,120,121],{},"www."," in one place and not the other is enough for a provider to refuse the\nsign-in. LokalBoards builds it from ",[105,124,125],{},"NUXT_BOARDS_URL",", so set that to the address\npeople actually use.",[30,128,130],{"id":129},"configuration","Configuration",[14,132,133],{},"Four variables are the minimum:",[98,135,139],{"className":136,"code":137,"language":138,"meta":107,"style":107},"language-dotenv shiki shiki-themes github-light github-dark","NUXT_SSO_ENABLED=true\nNUXT_SSO_ISSUER=https:\u002F\u002Flogin.example.com\u002Frealms\u002Fcompany\nNUXT_SSO_CLIENT_ID=lokalboards\nNUXT_SSO_CLIENT_SECRET=the-secret-your-provider-gave-you\n","dotenv",[105,140,141,149,155,161],{"__ignoreMap":107},[142,143,146],"span",{"class":144,"line":145},"line",1,[142,147,148],{},"NUXT_SSO_ENABLED=true\n",[142,150,152],{"class":144,"line":151},2,[142,153,154],{},"NUXT_SSO_ISSUER=https:\u002F\u002Flogin.example.com\u002Frealms\u002Fcompany\n",[142,156,158],{"class":144,"line":157},3,[142,159,160],{},"NUXT_SSO_CLIENT_ID=lokalboards\n",[142,162,164],{"class":144,"line":163},4,[142,165,166],{},"NUXT_SSO_CLIENT_SECRET=the-secret-your-provider-gave-you\n",[14,168,169,170,173],{},"Everything else — where to send people, where to exchange the code, where to ask\nfor a profile — is read from your provider's discovery document at\n",[105,171,172],{},"\u003Cissuer>\u002F.well-known\u002Fopenid-configuration",". If that URL returns JSON in your\nbrowser, the issuer is right.",[175,176,178],"h3",{"id":177},"all-the-settings","All the settings",[180,181,182,197],"table",{},[183,184,185],"thead",{},[186,187,188,192,195],"tr",{},[189,190,191],"th",{},"Variable",[189,193,194],{},"Default",[189,196,33],{},[198,199,200,216,229,241,253,268,283,304,316,332,344,359,374,389,401,418,430,442],"tbody",{},[186,201,202,208,213],{},[203,204,205],"td",{},[105,206,207],{},"NUXT_SSO_ENABLED",[203,209,210],{},[105,211,212],{},"false",[203,214,215],{},"Turns the button on.",[186,217,218,223,226],{},[203,219,220],{},[105,221,222],{},"NUXT_SSO_ISSUER",[203,224,225],{},"—",[203,227,228],{},"Your provider's issuer URL.",[186,230,231,236,238],{},[203,232,233],{},[105,234,235],{},"NUXT_SSO_CLIENT_ID",[203,237,225],{},[203,239,240],{},"The application you registered.",[186,242,243,248,250],{},[203,244,245],{},[105,246,247],{},"NUXT_SSO_CLIENT_SECRET",[203,249,225],{},[203,251,252],{},"Its secret. Server-side only; it never reaches a browser.",[186,254,255,260,265],{},[203,256,257],{},[105,258,259],{},"NUXT_SSO_LABEL",[203,261,262],{},[105,263,264],{},"Single sign-on",[203,266,267],{},"What the button says.",[186,269,270,275,280],{},[203,271,272],{},[105,273,274],{},"NUXT_SSO_SCOPES",[203,276,277],{},[105,278,279],{},"openid profile email",[203,281,282],{},"Scopes to request.",[186,284,285,290,295],{},[203,286,287],{},[105,288,289],{},"NUXT_SSO_PROVISION",[203,291,292],{},[105,293,294],{},"auto",[203,296,297,299,300,303],{},[105,298,294],{}," creates accounts on first sign-in; ",[105,301,302],{},"existing"," only admits people who already have one.",[186,305,306,311,313],{},[203,307,308],{},[105,309,310],{},"NUXT_SSO_ALLOWED_DOMAINS",[203,312,225],{},[203,314,315],{},"Comma-separated e-mail domains allowed to sign in.",[186,317,318,323,325],{},[203,319,320],{},[105,321,322],{},"NUXT_SSO_ADMIN_CLAIM",[203,324,225],{},[203,326,327,328,331],{},"A claim to read the administrator role from, e.g. ",[105,329,330],{},"groups",".",[186,333,334,339,341],{},[203,335,336],{},[105,337,338],{},"NUXT_SSO_ADMIN_VALUE",[203,340,225],{},[203,342,343],{},"The value in that claim that means \"administrator\".",[186,345,346,351,356],{},[203,347,348],{},[105,349,350],{},"NUXT_SSO_CLAIM_SUBJECT",[203,352,353],{},[105,354,355],{},"sub",[203,357,358],{},"Which field holds the provider's own id for a person.",[186,360,361,366,371],{},[203,362,363],{},[105,364,365],{},"NUXT_SSO_CLAIM_EMAIL",[203,367,368],{},[105,369,370],{},"email,preferred_username",[203,372,373],{},"Which field holds the e-mail address.",[186,375,376,381,386],{},[203,377,378],{},[105,379,380],{},"NUXT_SSO_CLAIM_NAME",[203,382,383],{},[105,384,385],{},"name",[203,387,388],{},"Which field holds the display name.",[186,390,391,396,398],{},[203,392,393],{},[105,394,395],{},"NUXT_SSO_DOMAINS",[203,397,225],{},[203,399,400],{},"E-mail domains this provider signs in, for routing.",[186,402,403,408,410],{},[203,404,405],{},[105,406,407],{},"NUXT_SSO_PROVIDERS",[203,409,225],{},[203,411,412,413,331],{},"Names of further providers — see ",[414,415,417],"a",{"href":416},"#more-than-one-provider","more than one provider",[186,419,420,425,427],{},[203,421,422],{},[105,423,424],{},"NUXT_SSO_AUTHORIZATION_URL",[203,426,225],{},[203,428,429],{},"Only for providers without discovery.",[186,431,432,437,439],{},[203,433,434],{},[105,435,436],{},"NUXT_SSO_TOKEN_URL",[203,438,225],{},[203,440,441],{},"The same. Set it together with the one above.",[186,443,444,449,451],{},[203,445,446],{},[105,447,448],{},"NUXT_SSO_USERINFO_URL",[203,450,225],{},[203,452,453],{},"The same, optional.",[14,455,456,457,460],{},"A half-configured instance behaves as if SSO were off: no button, and both\nendpoints answer ",[105,458,459],{},"404",". A button that leads to an error page is worse than no\nbutton.",[30,462,464],{"id":463},"your-provider","Your provider",[14,466,467],{},"The four values are the same everywhere. What differs is where to find them.",[175,469,471],{"id":470},"microsoft-entra-id-azure-ad","Microsoft Entra ID (Azure AD)",[473,474,475,480,490,505],"ol",{},[38,476,477],{},[18,478,479],{},"Microsoft Entra admin centre → App registrations → New registration.",[38,481,482,483,486,487,331],{},"Redirect URI: ",[18,484,485],{},"Web",", ",[105,488,489],{},"https:\u002F\u002Fboards.example.com\u002Fapi\u002Fauth\u002Fsso\u002Fcallback",[38,491,492,493,496,497,501,502,331],{},"From ",[18,494,495],{},"Overview",", take the ",[498,499,500],"em",{},"Application (client) ID"," and the ",[498,503,504],{},"Directory\n(tenant) ID",[38,506,507,510],{},[18,508,509],{},"Certificates & secrets → New client secret."," Copy the value now; it is\nshown once.",[98,512,514],{"className":136,"code":513,"language":138,"meta":107,"style":107},"NUXT_SSO_ISSUER=https:\u002F\u002Flogin.microsoftonline.com\u002F\u003Ctenant-id>\u002Fv2.0\nNUXT_SSO_CLIENT_ID=\u003Capplication-id>\nNUXT_SSO_CLIENT_SECRET=\u003Csecret-value>\nNUXT_SSO_LABEL=Sign in with Microsoft\n",[105,515,516,521,526,531],{"__ignoreMap":107},[142,517,518],{"class":144,"line":145},[142,519,520],{},"NUXT_SSO_ISSUER=https:\u002F\u002Flogin.microsoftonline.com\u002F\u003Ctenant-id>\u002Fv2.0\n",[142,522,523],{"class":144,"line":151},[142,524,525],{},"NUXT_SSO_CLIENT_ID=\u003Capplication-id>\n",[142,527,528],{"class":144,"line":157},[142,529,530],{},"NUXT_SSO_CLIENT_SECRET=\u003Csecret-value>\n",[142,532,533],{"class":144,"line":163},[142,534,535],{},"NUXT_SSO_LABEL=Sign in with Microsoft\n",[14,537,538,539,541,542,545],{},"For administrators from a group, add a ",[18,540,330],{}," claim under ",[498,543,544],{},"Token\nconfiguration"," and use the group's object id:",[98,547,549],{"className":136,"code":548,"language":138,"meta":107,"style":107},"NUXT_SSO_ADMIN_CLAIM=groups\nNUXT_SSO_ADMIN_VALUE=00000000-1111-2222-3333-444444444444\n",[105,550,551,556],{"__ignoreMap":107},[142,552,553],{"class":144,"line":145},[142,554,555],{},"NUXT_SSO_ADMIN_CLAIM=groups\n",[142,557,558],{"class":144,"line":151},[142,559,560],{},"NUXT_SSO_ADMIN_VALUE=00000000-1111-2222-3333-444444444444\n",[175,562,564],{"id":563},"google-workspace","Google Workspace",[473,566,567,572,577],{},[38,568,569],{},[18,570,571],{},"Google Cloud console → APIs & Services → Credentials → Create credentials →\nOAuth client ID → Web application.",[38,573,574,575,331],{},"Authorised redirect URI: ",[105,576,489],{},[38,578,579,580,583,584,587],{},"Under ",[18,581,582],{},"OAuth consent screen",", set the publishing status to ",[498,585,586],{},"Internal"," so\nonly your own organisation can use it.",[98,589,591],{"className":136,"code":590,"language":138,"meta":107,"style":107},"NUXT_SSO_ISSUER=https:\u002F\u002Faccounts.google.com\nNUXT_SSO_CLIENT_ID=\u003Cid>.apps.googleusercontent.com\nNUXT_SSO_CLIENT_SECRET=\u003Csecret>\nNUXT_SSO_LABEL=Sign in with Google\nNUXT_SSO_ALLOWED_DOMAINS=example.com\n",[105,592,593,598,603,608,613],{"__ignoreMap":107},[142,594,595],{"class":144,"line":145},[142,596,597],{},"NUXT_SSO_ISSUER=https:\u002F\u002Faccounts.google.com\n",[142,599,600],{"class":144,"line":151},[142,601,602],{},"NUXT_SSO_CLIENT_ID=\u003Cid>.apps.googleusercontent.com\n",[142,604,605],{"class":144,"line":157},[142,606,607],{},"NUXT_SSO_CLIENT_SECRET=\u003Csecret>\n",[142,609,610],{"class":144,"line":163},[142,611,612],{},"NUXT_SSO_LABEL=Sign in with Google\n",[142,614,616],{"class":144,"line":615},5,[142,617,618],{},"NUXT_SSO_ALLOWED_DOMAINS=example.com\n",[14,620,621],{},"Google does not send group memberships in the ID token, so leave the admin claim\nunset and manage administrators here.",[175,623,625],{"id":624},"okta","Okta",[473,627,628,633,638],{},[38,629,630],{},[18,631,632],{},"Admin console → Applications → Create App Integration → OIDC → Web\nApplication.",[38,634,635,636,331],{},"Sign-in redirect URI: ",[105,637,489],{},[38,639,640],{},"Assign the application to the people or groups who should have access.",[98,642,644],{"className":136,"code":643,"language":138,"meta":107,"style":107},"NUXT_SSO_ISSUER=https:\u002F\u002Fexample.okta.com\u002Foauth2\u002Fdefault\nNUXT_SSO_CLIENT_ID=\u003Cclient-id>\nNUXT_SSO_CLIENT_SECRET=\u003Cclient-secret>\nNUXT_SSO_LABEL=Sign in with Okta\n",[105,645,646,651,656,661],{"__ignoreMap":107},[142,647,648],{"class":144,"line":145},[142,649,650],{},"NUXT_SSO_ISSUER=https:\u002F\u002Fexample.okta.com\u002Foauth2\u002Fdefault\n",[142,652,653],{"class":144,"line":151},[142,654,655],{},"NUXT_SSO_CLIENT_ID=\u003Cclient-id>\n",[142,657,658],{"class":144,"line":157},[142,659,660],{},"NUXT_SSO_CLIENT_SECRET=\u003Cclient-secret>\n",[142,662,663],{"class":144,"line":163},[142,664,665],{},"NUXT_SSO_LABEL=Sign in with Okta\n",[14,667,668,669,671],{},"Add a ",[105,670,330],{}," claim to the ID token in the authorisation server's claims\nsettings if you want administrators from a group.",[175,673,675],{"id":674},"keycloak","Keycloak",[473,677,678,690,695],{},[38,679,680,683,684,686,687,331],{},[18,681,682],{},"Clients → Create client",", type ",[498,685,20],{},", client authentication\n",[18,688,689],{},"on",[38,691,692,693,331],{},"Valid redirect URI: ",[105,694,489],{},[38,696,697,698,331],{},"The secret is under ",[18,699,700],{},"Credentials",[98,702,704],{"className":136,"code":703,"language":138,"meta":107,"style":107},"NUXT_SSO_ISSUER=https:\u002F\u002Fsso.example.com\u002Frealms\u002Fcompany\nNUXT_SSO_CLIENT_ID=lokalboards\nNUXT_SSO_CLIENT_SECRET=\u003Csecret>\n",[105,705,706,711,715],{"__ignoreMap":107},[142,707,708],{"class":144,"line":145},[142,709,710],{},"NUXT_SSO_ISSUER=https:\u002F\u002Fsso.example.com\u002Frealms\u002Fcompany\n",[142,712,713],{"class":144,"line":151},[142,714,160],{},[142,716,717],{"class":144,"line":157},[142,718,607],{},[14,720,721,722,725,726,728,729,732,733,736],{},"For administrators, add a ",[18,723,724],{},"group membership"," mapper named ",[105,727,330],{}," to the\nclient's dedicated scope, then set ",[105,730,731],{},"NUXT_SSO_ADMIN_CLAIM=groups"," and\n",[105,734,735],{},"NUXT_SSO_ADMIN_VALUE=\u002Flokalboards-admins",". Keycloak sends group paths with a\nleading slash — use exactly what appears in the token.",[175,738,740],{"id":739},"authentik","Authentik",[473,742,743,753,757],{},[38,744,745,748,749,752],{},[18,746,747],{},"Applications → Providers → Create → OAuth2\u002FOpenID Provider",", authorisation\nflow ",[498,750,751],{},"implicit consent"," (or explicit, as you prefer).",[38,754,482,755,331],{},[105,756,489],{},[38,758,759,760,763],{},"Create an ",[18,761,762],{},"Application"," bound to that provider so it appears in the user\nportal.",[98,765,767],{"className":136,"code":766,"language":138,"meta":107,"style":107},"NUXT_SSO_ISSUER=https:\u002F\u002Fauth.example.com\u002Fapplication\u002Fo\u002Flokalboards\u002F\nNUXT_SSO_CLIENT_ID=\u003Cclient-id>\nNUXT_SSO_CLIENT_SECRET=\u003Cclient-secret>\n",[105,768,769,774,778],{"__ignoreMap":107},[142,770,771],{"class":144,"line":145},[142,772,773],{},"NUXT_SSO_ISSUER=https:\u002F\u002Fauth.example.com\u002Fapplication\u002Fo\u002Flokalboards\u002F\n",[142,775,776],{"class":144,"line":151},[142,777,655],{},[142,779,780],{"class":144,"line":157},[142,781,660],{},[175,783,785],{"id":784},"auth0","Auth0",[98,787,789],{"className":136,"code":788,"language":138,"meta":107,"style":107},"NUXT_SSO_ISSUER=https:\u002F\u002Fexample.eu.auth0.com\u002F\nNUXT_SSO_CLIENT_ID=\u003Cclient-id>\nNUXT_SSO_CLIENT_SECRET=\u003Cclient-secret>\n",[105,790,791,796,800],{"__ignoreMap":107},[142,792,793],{"class":144,"line":145},[142,794,795],{},"NUXT_SSO_ISSUER=https:\u002F\u002Fexample.eu.auth0.com\u002F\n",[142,797,798],{"class":144,"line":151},[142,799,655],{},[142,801,802],{"class":144,"line":157},[142,803,660],{},[14,805,806,807,331],{},"Allowed callback URL: ",[105,808,489],{},[175,810,812],{"id":811},"anything-else","Anything else",[14,814,815],{},"Any provider with a discovery document works with the four basic variables. One\nwithout can be configured by hand:",[98,817,819],{"className":136,"code":818,"language":138,"meta":107,"style":107},"NUXT_SSO_AUTHORIZATION_URL=https:\u002F\u002Fsso.example.com\u002Foauth2\u002Fauthorize\nNUXT_SSO_TOKEN_URL=https:\u002F\u002Fsso.example.com\u002Foauth2\u002Ftoken\nNUXT_SSO_USERINFO_URL=https:\u002F\u002Fsso.example.com\u002Foauth2\u002Fuserinfo\nNUXT_SSO_ISSUER=https:\u002F\u002Fsso.example.com\n",[105,820,821,826,831,836],{"__ignoreMap":107},[142,822,823],{"class":144,"line":145},[142,824,825],{},"NUXT_SSO_AUTHORIZATION_URL=https:\u002F\u002Fsso.example.com\u002Foauth2\u002Fauthorize\n",[142,827,828],{"class":144,"line":151},[142,829,830],{},"NUXT_SSO_TOKEN_URL=https:\u002F\u002Fsso.example.com\u002Foauth2\u002Ftoken\n",[142,832,833],{"class":144,"line":157},[142,834,835],{},"NUXT_SSO_USERINFO_URL=https:\u002F\u002Fsso.example.com\u002Foauth2\u002Fuserinfo\n",[142,837,838],{"class":144,"line":163},[142,839,840],{},"NUXT_SSO_ISSUER=https:\u002F\u002Fsso.example.com\n",[14,842,843],{},"The issuer is still needed: it is what the tokens are checked against.",[175,845,847],{"id":846},"plain-oauth-20-and-providers-that-name-things-differently","Plain OAuth 2.0, and providers that name things differently",[14,849,850,851,854,855,858,859,854,861,863],{},"Not everything is OpenID Connect. A plain OAuth 2.0 provider issues no ID token\nand answers its profile endpoint with whatever field names it likes — GitHub\nsends ",[105,852,853],{},"id"," and ",[105,856,857],{},"login",", not ",[105,860,355],{},[105,862,385],{},". Three settings say where to look:",[98,865,867],{"className":136,"code":866,"language":138,"meta":107,"style":107},"NUXT_SSO_CLAIM_SUBJECT=id\nNUXT_SSO_CLAIM_EMAIL=email\nNUXT_SSO_CLAIM_NAME=login\n",[105,868,869,874,879],{"__ignoreMap":107},[142,870,871],{"class":144,"line":145},[142,872,873],{},"NUXT_SSO_CLAIM_SUBJECT=id\n",[142,875,876],{"class":144,"line":151},[142,877,878],{},"NUXT_SSO_CLAIM_EMAIL=email\n",[142,880,881],{"class":144,"line":157},[142,882,883],{},"NUXT_SSO_CLAIM_NAME=login\n",[14,885,886,889],{},[18,887,888],{},"Several candidates",", comma separated and tried in order, for providers that\nleave their first choice empty rather than absent:",[98,891,893],{"className":136,"code":892,"language":138,"meta":107,"style":107},"NUXT_SSO_CLAIM_EMAIL=email,primary_email,mail\n",[105,894,895],{"__ignoreMap":107},[142,896,897],{"class":144,"line":145},[142,898,892],{},[14,900,901,904],{},[18,902,903],{},"Nested fields",", with dots, for providers that wrap everything:",[98,906,908],{"className":136,"code":907,"language":138,"meta":107,"style":107},"NUXT_SSO_CLAIM_NAME=data.attributes.display_name\n",[105,909,910],{"__ignoreMap":107},[142,911,912],{"class":144,"line":145},[142,913,907],{},[14,915,916],{},"The subject is what an account is linked by, so choose a field the provider will\nnever change — a numeric id rather than a username somebody can edit.",[14,918,919],{},"Without a mapping, a provider that sends none of the standard names is refused\nrather than guessed at: better a clear \"no e-mail address\" than an account keyed\nto the wrong thing.",[14,921,922,925,926,929,930,933],{},[18,923,924],{},"One caution before wiring up a provider this way."," LokalBoards links a\nfirst-time sign-in to any existing account with the same e-mail address, which is\nonly safe if the provider vouches for that address. Check that its profile\nendpoint returns a ",[498,927,928],{},"verified"," one. GitHub, for instance, needs the ",[105,931,932],{},"user:email","\nscope and can still answer with a null address when somebody keeps theirs\nprivate.",[30,935,24],{"id":936},"saml-20",[14,938,939],{},"Everything above is OpenID Connect. If your provider speaks SAML instead, this\nsection replaces the configuration — the rest of the page (who gets in, existing\naccounts, administrators) applies just the same.",[175,941,130],{"id":942},"configuration-1",[98,944,946],{"className":136,"code":945,"language":138,"meta":107,"style":107},"NUXT_SAML_ENABLED=true\nNUXT_SAML_ENTRY_POINT=https:\u002F\u002Fidp.example.com\u002Fidp\u002Fprofile\u002FSAML2\u002FRedirect\u002FSSO\nNUXT_SAML_IDP_CERT=MIIDdzCCAl+gAwIBAgIEb...\nNUXT_SAML_LABEL=Sign in with the university\n",[105,947,948,953,958,963],{"__ignoreMap":107},[142,949,950],{"class":144,"line":145},[142,951,952],{},"NUXT_SAML_ENABLED=true\n",[142,954,955],{"class":144,"line":151},[142,956,957],{},"NUXT_SAML_ENTRY_POINT=https:\u002F\u002Fidp.example.com\u002Fidp\u002Fprofile\u002FSAML2\u002FRedirect\u002FSSO\n",[142,959,960],{"class":144,"line":157},[142,961,962],{},"NUXT_SAML_IDP_CERT=MIIDdzCCAl+gAwIBAgIEb...\n",[142,964,965],{"class":144,"line":163},[142,966,967],{},"NUXT_SAML_LABEL=Sign in with the university\n",[14,969,970,973,974,977,978,981,982,985],{},[105,971,972],{},"NUXT_SAML_IDP_CERT"," is the provider's ",[18,975,976],{},"signing certificate",", which you will\nfind in its metadata as ",[105,979,980],{},"\u003CX509Certificate>",". Paste it with or without the\n",[105,983,984],{},"-----BEGIN CERTIFICATE-----"," lines; whitespace does not matter. During a\ncertificate rollover, give both separated by a comma and each is accepted until\nthe old one is retired.",[175,987,989],{"id":988},"what-to-give-your-provider","What to give your provider",[180,991,992,1000],{},[183,993,994],{},[186,995,996,998],{},[189,997],{},[189,999],{},[198,1001,1002,1015,1027,1037,1047],{},[186,1003,1004,1010],{},[203,1005,1006,1009],{},[18,1007,1008],{},"Entity ID"," (audience)",[203,1011,1012],{},[105,1013,1014],{},"https:\u002F\u002Fboards.example.com",[186,1016,1017,1022],{},[203,1018,1019],{},[18,1020,1021],{},"ACS \u002F Reply URL",[203,1023,1024],{},[105,1025,1026],{},"https:\u002F\u002Fboards.example.com\u002Fapi\u002Fauth\u002Fsaml\u002Facs",[186,1028,1029,1034],{},[203,1030,1031],{},[18,1032,1033],{},"Binding",[203,1035,1036],{},"HTTP-POST",[186,1038,1039,1044],{},[203,1040,1041],{},[18,1042,1043],{},"Name ID format",[203,1045,1046],{},"E-mail address",[186,1048,1049,1054],{},[203,1050,1051],{},[18,1052,1053],{},"Metadata",[203,1055,1056],{},[105,1057,1058],{},"https:\u002F\u002Fboards.example.com\u002Fapi\u002Fauth\u002Fsaml\u002Fmetadata",[14,1060,1061,1062,1064,1065,1068],{},"Most providers can be pointed at that metadata URL and will configure themselves\nfrom it. The entity ID defaults to ",[105,1063,125],{},"; set ",[105,1066,1067],{},"NUXT_SAML_ENTITY_ID","\nif your provider needs a different one.",[175,1070,1072],{"id":1071},"all-the-saml-settings","All the SAML settings",[180,1074,1075,1085],{},[183,1076,1077],{},[186,1078,1079,1081,1083],{},[189,1080,191],{},[189,1082,194],{},[189,1084,33],{},[198,1086,1087,1100,1112,1123,1135,1148,1162,1175,1187,1202,1217,1229,1246,1258,1272,1284,1296,1310,1321,1336,1349,1363],{},[186,1088,1089,1094,1098],{},[203,1090,1091],{},[105,1092,1093],{},"NUXT_SAML_ENABLED",[203,1095,1096],{},[105,1097,212],{},[203,1099,215],{},[186,1101,1102,1107,1109],{},[203,1103,1104],{},[105,1105,1106],{},"NUXT_SAML_ENTRY_POINT",[203,1108,225],{},[203,1110,1111],{},"The provider's SSO URL, where people are sent to sign in.",[186,1113,1114,1118,1120],{},[203,1115,1116],{},[105,1117,972],{},[203,1119,225],{},[203,1121,1122],{},"Its signing certificate. Several, comma separated, during a rollover.",[186,1124,1125,1130,1132],{},[203,1126,1127],{},[105,1128,1129],{},"NUXT_SAML_IDP_ISSUER",[203,1131,225],{},[203,1133,1134],{},"The provider's entity id. Set it: assertions from any other issuer are then refused.",[186,1136,1137,1141,1145],{},[203,1138,1139],{},[105,1140,1067],{},[203,1142,1143],{},[105,1144,125],{},[203,1146,1147],{},"Our entity id, and the audience assertions must name.",[186,1149,1150,1155,1160],{},[203,1151,1152],{},[105,1153,1154],{},"NUXT_SAML_LABEL",[203,1156,1157],{},[105,1158,1159],{},"SAML single sign-on",[203,1161,267],{},[186,1163,1164,1169,1172],{},[203,1165,1166],{},[105,1167,1168],{},"NUXT_SAML_ATTRIBUTE_EMAIL",[203,1170,1171],{},"common spellings",[203,1173,1174],{},"Which attribute holds the address.",[186,1176,1177,1182,1184],{},[203,1178,1179],{},[105,1180,1181],{},"NUXT_SAML_ATTRIBUTE_NAME",[203,1183,1171],{},[203,1185,1186],{},"Which attribute holds the display name.",[186,1188,1189,1194,1199],{},[203,1190,1191],{},[105,1192,1193],{},"NUXT_SAML_ATTRIBUTE_SUBJECT",[203,1195,1196],{},[105,1197,1198],{},"nameID",[203,1200,1201],{},"What the account is linked by.",[186,1203,1204,1209,1211],{},[203,1205,1206],{},[105,1207,1208],{},"NUXT_SAML_ADMIN_ATTRIBUTE",[203,1210,225],{},[203,1212,1213,1214,331],{},"An attribute to read the administrator role from, e.g. ",[105,1215,1216],{},"memberOf",[186,1218,1219,1224,1226],{},[203,1220,1221],{},[105,1222,1223],{},"NUXT_SAML_ADMIN_VALUE",[203,1225,225],{},[203,1227,1228],{},"The value in it that means \"administrator\".",[186,1230,1231,1236,1239],{},[203,1232,1233],{},[105,1234,1235],{},"NUXT_SAML_PROVISION",[203,1237,1238],{},"follows OIDC",[203,1240,1241,21,1243,1245],{},[105,1242,294],{},[105,1244,302],{},", as above.",[186,1247,1248,1253,1255],{},[203,1249,1250],{},[105,1251,1252],{},"NUXT_SAML_ALLOWED_DOMAINS",[203,1254,1238],{},[203,1256,1257],{},"As above.",[186,1259,1260,1265,1269],{},[203,1261,1262],{},[105,1263,1264],{},"NUXT_SAML_WANT_RESPONSE_SIGNED",[203,1266,1267],{},[105,1268,212],{},[203,1270,1271],{},"Also require the response around the assertion to be signed.",[186,1273,1274,1279,1281],{},[203,1275,1276],{},[105,1277,1278],{},"NUXT_SAML_DECRYPTION_KEY",[203,1280,225],{},[203,1282,1283],{},"Private key for encrypted assertions.",[186,1285,1286,1291,1293],{},[203,1287,1288],{},[105,1289,1290],{},"NUXT_SAML_DECRYPTION_CERT",[203,1292,225],{},[203,1294,1295],{},"Its certificate, published in our metadata.",[186,1297,1298,1303,1307],{},[203,1299,1300],{},[105,1301,1302],{},"NUXT_SAML_ALLOW_IDP_INITIATED",[203,1304,1305],{},[105,1306,212],{},[203,1308,1309],{},"Accept sign-ins started at the provider.",[186,1311,1312,1317,1319],{},[203,1313,1314],{},[105,1315,1316],{},"NUXT_SAML_DOMAINS",[203,1318,225],{},[203,1320,400],{},[186,1322,1323,1328,1333],{},[203,1324,1325],{},[105,1326,1327],{},"NUXT_SAML_CLOCK_SKEW_SECONDS",[203,1329,1330],{},[105,1331,1332],{},"60",[203,1334,1335],{},"Tolerance for clocks that disagree.",[186,1337,1338,1343,1346],{},[203,1339,1340],{},[105,1341,1342],{},"NUXT_SAML_IDENTIFIER_FORMAT",[203,1344,1345],{},"e-mail address",[203,1347,1348],{},"The Name ID format to request.",[186,1350,1351,1356,1361],{},[203,1352,1353],{},[105,1354,1355],{},"NUXT_SAML_SIGNATURE_ALGORITHM",[203,1357,1358],{},[105,1359,1360],{},"sha256",[203,1362],{},[186,1364,1365,1370,1374],{},[203,1366,1367],{},[105,1368,1369],{},"NUXT_SAML_DISABLE_REQUESTED_AUTHN_CONTEXT",[203,1371,1372],{},[105,1373,212],{},[203,1375,1376],{},"Set for providers that refuse a requested authentication context (ADFS often does).",[175,1378,1380],{"id":1379},"attributes","Attributes",[14,1382,1383,1384,1387,1388,1391,1392,1395,1396,1399],{},"SAML attribute names are rarely friendly. An address might arrive as ",[105,1385,1386],{},"email",",\n",[105,1389,1390],{},"mail",", or ",[105,1393,1394],{},"urn:oid:0.9.2342.19200300.100.1.3","; Microsoft sends\n",[105,1397,1398],{},"http:\u002F\u002Fschemas.xmlsoap.org\u002Fws\u002F2005\u002F05\u002Fidentity\u002Fclaims\u002Femailaddress",". All of\nthose are recognised without configuration, along with the usual spellings for a\ndisplay name. Where your provider uses something else, say so:",[98,1401,1403],{"className":136,"code":1402,"language":138,"meta":107,"style":107},"NUXT_SAML_ATTRIBUTE_EMAIL=urn:oid:0.9.2342.19200300.100.1.3\nNUXT_SAML_ATTRIBUTE_NAME=urn:oid:2.16.840.1.113730.3.1.241\nNUXT_SAML_ADMIN_ATTRIBUTE=memberOf\nNUXT_SAML_ADMIN_VALUE=cn=lokalboards-admins,ou=groups,dc=example,dc=com\n",[105,1404,1405,1410,1415,1420],{"__ignoreMap":107},[142,1406,1407],{"class":144,"line":145},[142,1408,1409],{},"NUXT_SAML_ATTRIBUTE_EMAIL=urn:oid:0.9.2342.19200300.100.1.3\n",[142,1411,1412],{"class":144,"line":151},[142,1413,1414],{},"NUXT_SAML_ATTRIBUTE_NAME=urn:oid:2.16.840.1.113730.3.1.241\n",[142,1416,1417],{"class":144,"line":157},[142,1418,1419],{},"NUXT_SAML_ADMIN_ATTRIBUTE=memberOf\n",[142,1421,1422],{"class":144,"line":163},[142,1423,1424],{},"NUXT_SAML_ADMIN_VALUE=cn=lokalboards-admins,ou=groups,dc=example,dc=com\n",[14,1426,1427],{},"If the address is only in the Name ID and there is no attribute for it, nothing\nneeds configuring — the Name ID is used when no attribute carries an address.",[175,1429,1431],{"id":1430},"providers","Providers",[14,1433,1434,1437,1438,1441,1442,1445,1446,1449,1450,1453],{},[18,1435,1436],{},"Entra ID:"," ",[498,1439,1440],{},"Enterprise applications → New application → Create your own →\nIntegrate any other application",", then ",[498,1443,1444],{},"Single sign-on → SAML",". Identifier and\nReply URL from the table above. Take the ",[498,1447,1448],{},"Login URL"," and the\n",[498,1451,1452],{},"Certificate (Base64)"," from the SAML Certificates section.",[14,1455,1456,1437,1459,1462,1463,331],{},[18,1457,1458],{},"Okta:",[498,1460,1461],{},"Applications → Create App Integration → SAML 2.0",". Single sign-on URL\nis the ACS; Audience URI is the entity id. The certificate is under ",[498,1464,1465],{},"View SAML\nsetup instructions",[14,1467,1468,1437,1471,1474,1475,1477,1478,1480,1481,1484,1485,331],{},[18,1469,1470],{},"Keycloak:",[498,1472,1473],{},"Clients → Create client → SAML",". Client ID is our entity id, Valid\nredirect URI the ACS. Set ",[498,1476,1043],{}," to ",[105,1479,1386],{}," and turn ",[498,1482,1483],{},"Force POST\nbinding"," on. The realm's certificate is under ",[498,1486,1487],{},"Realm settings → Keys → RS256 →\nCertificate",[14,1489,1490,1493,1494,854,1497,1500,1501,331],{},[18,1491,1492],{},"ADFS:"," add a Relying Party Trust with the metadata URL, then add claim rules\nmapping LDAP attributes to ",[105,1495,1496],{},"E-Mail-Address",[105,1498,1499],{},"Name",". ADFS is the usual reason\nto set ",[105,1502,1503],{},"NUXT_SAML_DISABLE_REQUESTED_AUTHN_CONTEXT=true",[14,1505,1506,1509],{},[18,1507,1508],{},"Shibboleth, SimpleSAMLphp, PingFederate, OneLogin:"," register the metadata URL\nand release an e-mail attribute. Nothing else is special.",[175,1511,1513],{"id":1512},"what-is-checked-and-what-is-not","What is checked, and what is not",[14,1515,1516,1517,1520,1521,1523],{},"Every assertion must be ",[18,1518,1519],{},"signed",", and the signature must cover the assertion\nthat is then read — that is the defence against signature wrapping, the family\nof attacks that has produced authentication bypasses in SAML libraries for\nfifteen years. On top of that: the audience must be this instance, the\ndestination must be the ACS URL, the conditions window must be current (with the\nconfigured skew), and the issuer must match ",[105,1522,1129],{}," when one is\nset. Anything that fails is refused outright — there is no partial acceptance.",[14,1525,1526,1527,1535],{},"The signature checking itself is ",[414,1528,1532],{"href":1529,"rel":1530},"https:\u002F\u002Fgithub.com\u002Fnode-saml\u002Fnode-saml",[1531],"nofollow",[105,1533,1534],{},"@node-saml\u002Fnode-saml",",\nthe library the Node SAML ecosystem has hardened over many years. XML signature\nverification is the one part of this that should not be written from scratch for\nthe sake of one fewer dependency.",[175,1537,1539],{"id":1538},"encrypted-assertions","Encrypted assertions",[14,1541,1542],{},"Some providers encrypt assertions as a matter of policy. Give LokalBoards a key\npair of its own, and put the certificate where the provider can find it — it is\npublished in our metadata, so a provider pointed at the metadata URL picks it up\nby itself:",[98,1544,1548],{"className":1545,"code":1546,"language":1547,"meta":107,"style":107},"language-bash shiki shiki-themes github-light github-dark","openssl req -x509 -newkey rsa:2048 -nodes -days 3650 \\\n  -keyout saml-decrypt.key -out saml-decrypt.crt -subj \"\u002FCN=boards.example.com\"\n","bash",[105,1549,1550,1582],{"__ignoreMap":107},[142,1551,1552,1556,1560,1564,1567,1570,1573,1576,1579],{"class":144,"line":145},[142,1553,1555],{"class":1554},"sScJk","openssl",[142,1557,1559],{"class":1558},"sZZnC"," req",[142,1561,1563],{"class":1562},"sj4cs"," -x509",[142,1565,1566],{"class":1562}," -newkey",[142,1568,1569],{"class":1558}," rsa:2048",[142,1571,1572],{"class":1562}," -nodes",[142,1574,1575],{"class":1562}," -days",[142,1577,1578],{"class":1562}," 3650",[142,1580,1581],{"class":1562}," \\\n",[142,1583,1584,1587,1590,1593,1596,1599],{"class":144,"line":151},[142,1585,1586],{"class":1562},"  -keyout",[142,1588,1589],{"class":1558}," saml-decrypt.key",[142,1591,1592],{"class":1562}," -out",[142,1594,1595],{"class":1558}," saml-decrypt.crt",[142,1597,1598],{"class":1562}," -subj",[142,1600,1601],{"class":1558}," \"\u002FCN=boards.example.com\"\n",[98,1603,1605],{"className":136,"code":1604,"language":138,"meta":107,"style":107},"NUXT_SAML_DECRYPTION_KEY=-----BEGIN PRIVATE KEY-----\\nMIIEvg...\\n-----END PRIVATE KEY-----\nNUXT_SAML_DECRYPTION_CERT=MIIDdzCCAl+gAwIBAgIEb...\n",[105,1606,1607,1612],{"__ignoreMap":107},[142,1608,1609],{"class":144,"line":145},[142,1610,1611],{},"NUXT_SAML_DECRYPTION_KEY=-----BEGIN PRIVATE KEY-----\\nMIIEvg...\\n-----END PRIVATE KEY-----\n",[142,1613,1614],{"class":144,"line":151},[142,1615,1616],{},"NUXT_SAML_DECRYPTION_CERT=MIIDdzCCAl+gAwIBAgIEb...\n",[14,1618,1619,1620,1623],{},"Both accept the PEM armour or the bare base64, and ",[105,1621,1622],{},"\\n"," escapes survive a Docker\nenvironment file. Assertions are decrypted before anything else is looked at; an\nunencrypted one still works, so switching this on does not break a provider\nmid-change.",[175,1625,1627],{"id":1626},"sign-in-started-at-your-provider","Sign-in started at your provider",[14,1629,1630],{},"Somebody clicking your application's tile in Entra's My Apps or Okta's dashboard\narrives here with an assertion nobody asked for. That is refused by default and\nenabled with:",[98,1632,1634],{"className":136,"code":1633,"language":138,"meta":107,"style":107},"NUXT_SAML_ALLOW_IDP_INITIATED=true\n",[105,1635,1636],{"__ignoreMap":107},[142,1637,1638],{"class":144,"line":145},[142,1639,1633],{},[14,1641,1642,1643,1646],{},"The default is off deliberately. An assertion we did not request is a bearer\ntoken with nothing tying it to the browser presenting it, so when this is on,\neach assertion may be used exactly once — its identifier is recorded as it is\nconsumed and a second attempt is refused. ",[105,1644,1645],{},"RelayState"," is treated as untrusted\ntoo: anything that is not a path on this instance is ignored and the person\nlands on the dashboard, so a crafted response cannot turn this into an open\nredirect.",[14,1648,1649,1652],{},[18,1650,1651],{},"Still not implemented:"," single logout (SLO). Signing out of LokalBoards ends\nthe session here and leaves the session at your provider alone.",[30,1654,1656],{"id":1655},"more-than-one-provider","More than one provider",[14,1658,1659],{},"One provider is the common case and needs nothing beyond what is above. An\ninstance that needs several — a company with a partner directory, a merger, or a\nmigration where the old and the new provider both have to work for a while —\nnames them:",[98,1661,1663],{"className":136,"code":1662,"language":138,"meta":107,"style":107},"NUXT_SSO_PROVIDERS=acme,partner\n\nNUXT_SSO_ACME_ISSUER=https:\u002F\u002Flogin.microsoftonline.com\u002F\u003Ctenant>\u002Fv2.0\nNUXT_SSO_ACME_CLIENT_ID=...\nNUXT_SSO_ACME_CLIENT_SECRET=...\nNUXT_SSO_ACME_LABEL=Acme Corp\n\nNUXT_SSO_PARTNER_ISSUER=https:\u002F\u002Fpartner.example.com\u002Foidc\nNUXT_SSO_PARTNER_CLIENT_ID=...\nNUXT_SSO_PARTNER_CLIENT_SECRET=...\nNUXT_SSO_PARTNER_LABEL=Partner Ltd\n",[105,1664,1665,1670,1676,1681,1686,1691,1697,1702,1708,1714,1720],{"__ignoreMap":107},[142,1666,1667],{"class":144,"line":145},[142,1668,1669],{},"NUXT_SSO_PROVIDERS=acme,partner\n",[142,1671,1672],{"class":144,"line":151},[142,1673,1675],{"emptyLinePlaceholder":1674},true,"\n",[142,1677,1678],{"class":144,"line":157},[142,1679,1680],{},"NUXT_SSO_ACME_ISSUER=https:\u002F\u002Flogin.microsoftonline.com\u002F\u003Ctenant>\u002Fv2.0\n",[142,1682,1683],{"class":144,"line":163},[142,1684,1685],{},"NUXT_SSO_ACME_CLIENT_ID=...\n",[142,1687,1688],{"class":144,"line":615},[142,1689,1690],{},"NUXT_SSO_ACME_CLIENT_SECRET=...\n",[142,1692,1694],{"class":144,"line":1693},6,[142,1695,1696],{},"NUXT_SSO_ACME_LABEL=Acme Corp\n",[142,1698,1700],{"class":144,"line":1699},7,[142,1701,1675],{"emptyLinePlaceholder":1674},[142,1703,1705],{"class":144,"line":1704},8,[142,1706,1707],{},"NUXT_SSO_PARTNER_ISSUER=https:\u002F\u002Fpartner.example.com\u002Foidc\n",[142,1709,1711],{"class":144,"line":1710},9,[142,1712,1713],{},"NUXT_SSO_PARTNER_CLIENT_ID=...\n",[142,1715,1717],{"class":144,"line":1716},10,[142,1718,1719],{},"NUXT_SSO_PARTNER_CLIENT_SECRET=...\n",[142,1721,1723],{"class":144,"line":1722},11,[142,1724,1725],{},"NUXT_SSO_PARTNER_LABEL=Partner Ltd\n",[14,1727,1728,1729,486,1732,1735],{},"Every setting that exists on its own exists per provider under that provider's\nname: ",[105,1730,1731],{},"NUXT_SSO_ACME_SCOPES",[105,1733,1734],{},"NUXT_SSO_PARTNER_PROVISION",", and so on. Anything\nnot set for a provider falls back to the instance-wide value, so a shared policy\nis written once.",[14,1737,1738,1739,1742],{},"SAML providers are named the same way with ",[105,1740,1741],{},"NUXT_SAML_PROVIDERS",", and the two\nlists are independent — an instance can run two OpenID Connect providers and a\nSAML one at the same time. Each SAML provider gets its own reply URL, which is\nwhat you register at that provider's end:",[98,1744,1747],{"className":1745,"code":1746,"language":103},[101],"https:\u002F\u002Fboards.example.com\u002Fapi\u002Fauth\u002Fsaml\u002Facs?provider=university\nhttps:\u002F\u002Fboards.example.com\u002Fapi\u002Fauth\u002Fsaml\u002Fmetadata?provider=university\n",[105,1748,1746],{"__ignoreMap":107},[14,1750,1751,1752,1755,1756,1759],{},"Naming a provider switches it on. The unprefixed settings still work and are a\nprovider called ",[105,1753,1754],{},"sso"," (or ",[105,1757,1758],{},"saml",") alongside the named ones.",[30,1761,1763],{"id":1762},"sending-people-to-the-right-one","Sending people to the right one",[14,1765,1766],{},"With several providers, a row of buttons asks everyone to know which of their\norganisation's names is on theirs. Tell each provider which e-mail domains it\nsigns in, and typing an address is enough:",[98,1768,1770],{"className":136,"code":1769,"language":138,"meta":107,"style":107},"NUXT_SSO_ACME_DOMAINS=acme.example,acme.co.uk\nNUXT_SSO_PARTNER_DOMAINS=partner.example\nNUXT_SAML_UNIVERSITY_DOMAINS=uni.example\n",[105,1771,1772,1777,1782],{"__ignoreMap":107},[142,1773,1774],{"class":144,"line":145},[142,1775,1776],{},"NUXT_SSO_ACME_DOMAINS=acme.example,acme.co.uk\n",[142,1778,1779],{"class":144,"line":151},[142,1780,1781],{},"NUXT_SSO_PARTNER_DOMAINS=partner.example\n",[142,1783,1784],{"class":144,"line":157},[142,1785,1786],{},"NUXT_SAML_UNIVERSITY_DOMAINS=uni.example\n",[14,1788,1789],{},"As somebody types their address, the provider that claims that domain is offered\nfirst and filled in; the others stay below. Subdomains count, and the most\nspecific match wins, so a subsidiary's own provider beats the parent's\ncatch-all.",[14,1791,1792,1793,1796,1797,1799],{},"This is only about ",[498,1794,1795],{},"routing",". It refuses nobody — that is what\n",[105,1798,310],{}," is for — and the lookup says nothing about whether an\naccount exists, so it cannot be used to find out who uses this instance.",[30,1801,1803],{"id":1802},"who-gets-in","Who gets in",[14,1805,1806],{},"Two questions, and they are separate.",[14,1808,1809,1812],{},[18,1810,1811],{},"Who your provider lets through"," is your provider's business — assign the\napplication to a group, or to everyone, in its own admin interface. That is the\ncontrol you already have and the one to prefer.",[14,1814,1815,1818],{},[18,1816,1817],{},"Who LokalBoards accepts"," is this:",[98,1820,1822],{"className":136,"code":1821,"language":138,"meta":107,"style":107},"# Only these e-mail domains, whatever your provider allows\nNUXT_SSO_ALLOWED_DOMAINS=example.com,subsidiary.example.com\n\n# And\u002For: no new accounts, only people who already have one here\nNUXT_SSO_PROVISION=existing\n",[105,1823,1824,1829,1834,1838,1843],{"__ignoreMap":107},[142,1825,1826],{"class":144,"line":145},[142,1827,1828],{},"# Only these e-mail domains, whatever your provider allows\n",[142,1830,1831],{"class":144,"line":151},[142,1832,1833],{},"NUXT_SSO_ALLOWED_DOMAINS=example.com,subsidiary.example.com\n",[142,1835,1836],{"class":144,"line":157},[142,1837,1675],{"emptyLinePlaceholder":1674},[142,1839,1840],{"class":144,"line":163},[142,1841,1842],{},"# And\u002For: no new accounts, only people who already have one here\n",[142,1844,1845],{"class":144,"line":615},[142,1846,1847],{},"NUXT_SSO_PROVISION=existing\n",[14,1849,1850,1851,1853],{},"With ",[105,1852,302],{},", somebody your provider is happy to authenticate but who has no\naccount here is turned away with \"You do not have an account here yet\". Create\ntheir account first — by hand, or by inviting them to a board — and the next\nsign-in finds it.",[30,1855,1857],{"id":1856},"administrators-from-your-directory","Administrators from your directory",[98,1859,1861],{"className":136,"code":1860,"language":138,"meta":107,"style":107},"NUXT_SSO_ADMIN_CLAIM=groups\nNUXT_SSO_ADMIN_VALUE=lokalboards-admins\n",[105,1862,1863,1867],{"__ignoreMap":107},[142,1864,1865],{"class":144,"line":145},[142,1866,555],{},[142,1868,1869],{"class":144,"line":151},[142,1870,1871],{},"NUXT_SSO_ADMIN_VALUE=lokalboards-admins\n",[14,1873,1874],{},"On every sign-in, the claim is read and the role is set from it — both ways.\nSomeone added to the group becomes an administrator here at their next sign-in;\nsomeone removed from it stops being one. The claim may be a single value or a\nlist; a list matches if any entry does.",[14,1876,1877],{},"Leave both unset and roles are managed here instead, on the Users page. It is\none or the other: an instance that reads the claim will overwrite whatever you\nset by hand at the next sign-in.",[30,1879,1881],{"id":1880},"existing-accounts","Existing accounts",[14,1883,1884],{},"The first time somebody signs in through the provider, LokalBoards looks for an\naccount with the same e-mail address. If it finds one, the two are linked: same\naccount, same boards, same history, now reachable through SSO as well.",[14,1886,1887],{},"That linking is what makes SSO adoptable — a team that has been using the\ninstance for a year does not want to start again — and it is safe because the\naddress comes from the provider you configured, over a channel authenticated\nwith your client secret. It is never an address somebody typed into a form.",[14,1889,1890],{},"Their password keeps working, and so does the password form. There is no switch\nthat makes SSO the only way in, and there does not need to be: an account created\nby SSO has no password at all — nothing was ever set for it — so for everyone who\narrived through the provider, the provider already is the only way in. Accounts\nthat had a password keep it until an administrator changes it.",[14,1892,1893,1897,1898,1901],{},[414,1894,1896],{"href":1895},"\u002Fdocs\u002Fdisable-signup","Turning public signup off"," is a separate thing and does\nnot affect any of this: it stops strangers signing ",[498,1899,1900],{},"themselves"," up, and leaves\nthe password form, existing passwords and administrator-created accounts exactly\nas they were.",[30,1903,1905],{"id":1904},"turning-it-off","Turning it off",[14,1907,1908,1909,1912,1913,1915],{},"Set ",[105,1910,1911],{},"NUXT_SSO_ENABLED=false"," and restart. The button disappears, both endpoints\nanswer ",[105,1914,459],{},", and every account stays exactly as it was — the links to your\nprovider simply go unused. Nobody is locked out who has a password; anybody who\nonly ever used SSO needs one setting up.",[30,1917,1919],{"id":1918},"how-the-sign-in-works","How the sign-in works",[14,1921,1922],{},"Worth knowing if you are reviewing this before deploying it:",[473,1924,1925,1948,1955,1962,1965],{},[38,1926,1927,1928,1931,1932,1935,1936,1939,1940,1943,1944,1947],{},"The button is a plain link to ",[105,1929,1930],{},"\u002Fapi\u002Fauth\u002Fsso\u002Fstart",". That endpoint mints a\n",[18,1933,1934],{},"state",", a ",[18,1937,1938],{},"nonce"," and a ",[18,1941,1942],{},"PKCE verifier",", keeps them in ",[105,1945,1946],{},"httpOnly","\ncookies for ten minutes, and redirects to your provider.",[38,1949,1950,1951,1954],{},"Your provider authenticates the person and redirects back to\n",[105,1952,1953],{},"\u002Fapi\u002Fauth\u002Fsso\u002Fcallback"," with a one-time code.",[38,1956,1957,1958,1961],{},"The callback checks the state, then exchanges the code ",[18,1959,1960],{},"from the server",",\nauthenticating with the client secret and the PKCE verifier. No token ever\nreaches the browser.",[38,1963,1964],{},"The ID token's claims are checked: the issuer is the one configured, the\naudience is this client, it has not expired, and the nonce is the one from\nstep 1.",[38,1966,1967],{},"The account is found or created, a session is issued, and the browser lands\non the dashboard.",[14,1969,1970],{},"The ID token's signature is not verified, which OpenID Connect Core allows\n(§3.1.3.7) when the token came directly from the token endpoint over TLS to a\nclient that authenticated itself — the channel is the proof, and everything a\nsignature would not cover is checked anyway.",[30,1972,1974],{"id":1973},"when-it-does-not-work","When it does not work",[14,1976,1977],{},"Every failure sends the visitor back to the sign-in page with a message. What\neach one means:",[180,1979,1980,1990],{},[183,1981,1982],{},[186,1983,1984,1987],{},[189,1985,1986],{},"Message",[189,1988,1989],{},"Usually",[198,1991,1992,2006,2014,2029,2039,2047,2062,2072,2083,2094,2106,2117],{},[186,1993,1994,1997],{},[203,1995,1996],{},"The identity provider could not be reached",[203,1998,1999,2000,2002,2003,2005],{},"Wrong ",[105,2001,222],{},", or no route from the server to it. Check ",[105,2004,172],{}," from the server itself.",[186,2007,2008,2011],{},[203,2009,2010],{},"Your identity provider refused the sign-in",[203,2012,2013],{},"The person is not assigned to the application, or declined consent.",[186,2015,2016,2019],{},[203,2017,2018],{},"The response came from an unexpected identity provider",[203,2020,2021,2022,2024,2025,2028],{},"The issuer in the token does not match ",[105,2023,222],{}," — often a trailing slash, or Entra's ",[105,2026,2027],{},"v2.0"," suffix missing.",[186,2030,2031,2034],{},[203,2032,2033],{},"The response was not issued for this instance",[203,2035,2036,2038],{},[105,2037,235],{}," does not match the application the token was minted for.",[186,2040,2041,2044],{},[203,2042,2043],{},"This sign-in has expired",[203,2045,2046],{},"More than ten minutes between clicking and returning, or cookies blocked.",[186,2048,2049,2052],{},[203,2050,2051],{},"Your identity provider did not supply an e-mail address",[203,2053,2054,2055,2057,2058,331],{},"The ",[105,2056,1386],{}," scope is not granted, or the provider uses its own field names — see ",[414,2059,2061],{"href":2060},"#plain-oauth-20-and-providers-that-name-things-differently","plain OAuth 2.0",[186,2063,2064,2067],{},[203,2065,2066],{},"Your e-mail domain is not allowed",[203,2068,2069,2071],{},[105,2070,310],{}," does not include it.",[186,2073,2074,2077],{},[203,2075,2076],{},"You do not have an account here yet",[203,2078,2079,2082],{},[105,2080,2081],{},"NUXT_SSO_PROVISION=existing"," (or the SAML equivalent) and nobody has that address here.",[186,2084,2085,2091],{},[203,2086,2087,2088],{},"The sign-in could not be verified ",[498,2089,2090],{},"(SAML)",[203,2092,2093],{},"The assertion failed a check: wrong signing certificate, wrong audience, an expired window, or clocks too far apart. The server log names which.",[186,2095,2096,2101],{},[203,2097,2098,2099],{},"The response came from an unexpected identity provider ",[498,2100,2090],{},[203,2102,2103,2104,331],{},"The assertion's issuer does not match ",[105,2105,1129],{},[186,2107,2108,2111],{},[203,2109,2110],{},"This instance does not accept sign-ins started at your identity provider",[203,2112,2113,2114,2116],{},"Somebody arrived from a provider portal and ",[105,2115,1302],{}," is off.",[186,2118,2119,2122],{},[203,2120,2121],{},"This sign-in has already been used",[203,2123,2124],{},"A provider-initiated assertion was presented twice. Start again from the sign-in page.",[14,2126,2127,2128,2131,2132,2134,2135,2137],{},"A provider that rejects the request before showing a sign-in page — usually\n",[105,2129,2130],{},"redirect_uri_mismatch"," — never reaches LokalBoards at all. Compare the URI\nregistered with your provider against ",[105,2133,125],{}," plus\n",[105,2136,1953],{},", character by character.",[14,2139,2140],{},"Server logs carry the detail for anything unexpected; the browser is told only\nwhat is useful to the person in front of it.",[2142,2143,2144],"style",{},"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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":107,"searchDepth":151,"depth":151,"links":2146},[2147,2148,2149,2150,2153,2163,2173,2174,2175,2176,2177,2178,2179,2180],{"id":32,"depth":151,"text":33},{"id":76,"depth":151,"text":77},{"id":92,"depth":151,"text":93},{"id":129,"depth":151,"text":130,"children":2151},[2152],{"id":177,"depth":157,"text":178},{"id":463,"depth":151,"text":464,"children":2154},[2155,2156,2157,2158,2159,2160,2161,2162],{"id":470,"depth":157,"text":471},{"id":563,"depth":157,"text":564},{"id":624,"depth":157,"text":625},{"id":674,"depth":157,"text":675},{"id":739,"depth":157,"text":740},{"id":784,"depth":157,"text":785},{"id":811,"depth":157,"text":812},{"id":846,"depth":157,"text":847},{"id":936,"depth":151,"text":24,"children":2164},[2165,2166,2167,2168,2169,2170,2171,2172],{"id":942,"depth":157,"text":130},{"id":988,"depth":157,"text":989},{"id":1071,"depth":157,"text":1072},{"id":1379,"depth":157,"text":1380},{"id":1430,"depth":157,"text":1431},{"id":1512,"depth":157,"text":1513},{"id":1538,"depth":157,"text":1539},{"id":1626,"depth":157,"text":1627},{"id":1655,"depth":151,"text":1656},{"id":1762,"depth":151,"text":1763},{"id":1802,"depth":151,"text":1803},{"id":1856,"depth":151,"text":1857},{"id":1880,"depth":151,"text":1881},{"id":1904,"depth":151,"text":1905},{"id":1918,"depth":151,"text":1919},{"id":1973,"depth":151,"text":1974},"LokalBoards signs people in against your own identity provider — Entra ID,\nGoogle Workspace, Okta, Keycloak, Authentik, Auth0, Zitadel, ADFS, Shibboleth,\nor anything else that speaks OpenID Connect or SAML 2.0. One button on\nthe sign-in page, no password to keep here, and joiners and leavers handled\nwhere you already handle them.","md",{},"\u002Fdocs\u002Fsingle-sign-on",{"title":5,"description":2181},"docs\u002F08.single-sign-on","HwTHKM3lnFIYReWwePqQYjjmaqQ0KUDRzIaTE1PpMG4",[2189],{"title":2190,"path":2191,"stem":2192,"children":2193},"Docs","\u002Fdocs","docs",[2194,2197,2201,2205,2209,2213,2217,2220,2221,2225,2229,2233,2237],{"title":2195,"path":2191,"stem":2196},"Getting started","docs\u002Findex",{"title":2198,"path":2199,"stem":2200},"Boards","\u002Fdocs\u002Fboards","docs\u002F02.boards",{"title":2202,"path":2203,"stem":2204},"Areas","\u002Fdocs\u002Fareas","docs\u002F03.areas",{"title":2206,"path":2207,"stem":2208},"Cards","\u002Fdocs\u002Fcards","docs\u002F04.cards",{"title":2210,"path":2211,"stem":2212},"Comments","\u002Fdocs\u002Fcomments","docs\u002F05.comments",{"title":2214,"path":2215,"stem":2216},"Users","\u002Fdocs\u002Fusers","docs\u002F06.users",{"title":2218,"path":1895,"stem":2219},"Disable Signup","docs\u002F07.disable-signup",{"title":5,"path":2184,"stem":2186},{"title":2222,"path":2223,"stem":2224},"Adjust Colors","\u002Fdocs\u002Fadjust-colors","docs\u002F09.adjust-colors",{"title":2226,"path":2227,"stem":2228},"MCP Server","\u002Fdocs\u002Fmcp-server","docs\u002F10.mcp-server",{"title":2230,"path":2231,"stem":2232},"Migration","\u002Fdocs\u002Fmigration","docs\u002F11.migration",{"title":2234,"path":2235,"stem":2236},"Health Check","\u002Fdocs\u002Fhealth-check","docs\u002F12.health-check",{"title":2238,"path":2239,"stem":2240},"Contributing","\u002Fdocs\u002Fcontributing","docs\u002F13.contributing",[2242],{"title":2243,"path":2244,"stem":2245,"children":2246},"Api","\u002Fapi","api",[2247,2250,2253,2257,2260,2264,2267,2271,2275,2279,2283],{"title":2248,"path":2244,"stem":2249},"Authentication","api\u002Findex",{"title":2198,"path":2251,"stem":2252},"\u002Fapi\u002Fboards","api\u002F02.boards",{"title":2254,"path":2255,"stem":2256},"Board","\u002Fapi\u002Fboard","api\u002F03.board",{"title":2202,"path":2258,"stem":2259},"\u002Fapi\u002Fareas","api\u002F04.areas",{"title":2261,"path":2262,"stem":2263},"Area","\u002Fapi\u002Farea","api\u002F05.area",{"title":2206,"path":2265,"stem":2266},"\u002Fapi\u002Fcards","api\u002F06.cards",{"title":2268,"path":2269,"stem":2270},"Card","\u002Fapi\u002Fcard","api\u002F07.card",{"title":2272,"path":2273,"stem":2274},"Move a card","\u002Fapi\u002Fcard-move","api\u002F08.card-move",{"title":2276,"path":2277,"stem":2278},"Order a card","\u002Fapi\u002Fcard-order","api\u002F09.card-order",{"title":2280,"path":2281,"stem":2282},"Comment","\u002Fapi\u002Fcomment","api\u002F10.comment",{"title":2284,"path":2285,"stem":2286},"Invite","\u002Fapi\u002Finvite","api\u002F11.invite",1787439222290]