Single sign-on (SSO / OIDC)
If you already run an identity provider (the service that holds everyone’s logins, like Keycloak, Authentik, Authelia, or a commercial provider), PhotoStructure can hand sign-in off to it. Your family logs in once, with the account they already have, and a “Sign in with SSO” button appears on the PhotoStructure login screen.
🤓 You may not need this
SSO is for people who already run an identity provider. If you just want a login screen for your household, passwordless sign-in codes are simpler and need nothing extra to set up.
This works through OpenID Connect (OIDC), the standard layer on top of OAuth2 that nearly every identity provider speaks. PhotoStructure uses the Authorization Code flow with PKCE, so it’s safe to run even without a client secret.
🧳 Before you begin
- Turn on user accounts first. SSO is layered on top of PhotoStructure’s user accounts; it does nothing until accounts are enabled. If accounts are off, start there.
- You’ll configure this as an admin. SSO settings live with everything else on the Settings page, which only admins can reach, so you need a working admin account (created with a sign-in code or password) before you connect a provider.
- Any OIDC-compliant provider works. Keycloak, Authentik, and Authelia are the common self-hosted choices; Google Workspace, Microsoft Entra ID, and Okta are common hosted ones.
🎯 The one URL you have to get right
The single most common reason SSO setup fails is a mismatched redirect URL (also called the callback or reply URL): the address your provider sends people back to after they log in. For PhotoStructure it is always:
https://your-photostructure-address/auth/oidc/callback
Replace your-photostructure-address with however you reach PhotoStructure from
a browser, for example https://photos.example.com/auth/oidc/callback. If you
reach it directly without HTTPS, it’s http://your-host:1787/auth/oidc/callback,
but put SSO behind HTTPS (see the security notes).
Your provider will insist this matches exactly, character for character. If
PhotoStructure runs behind a reverse proxy, also set publicBaseUrl (see
step 2) so PhotoStructure
builds the same URL your provider expects.
✍️ Step 1: Register PhotoStructure with your provider
In your identity provider, create a new application (providers variously call it a “client,” “app registration,” or “relying party”). You’ll come away with three values PhotoStructure needs:
- Issuer URL — the base address of your provider, e.g.
https://keycloak.example.com/realms/family. PhotoStructure reads<issuer>/.well-known/openid-configurationfrom it to discover everything else automatically. - Client ID — the identifier your provider assigns to PhotoStructure.
- Client secret — a password for the app. You can leave this empty and use PKCE alone if your provider supports a “public” client.
While you’re there, set the app’s redirect URL to the
/auth/oidc/callback address above.
🗣️ Step 2: Tell PhotoStructure about your provider
You can set these three ways. All are equivalent; pick whichever fits how you run PhotoStructure.
A. The Settings page (easiest). Sign in as an admin, open Settings, and fill in the OIDC fields. Changes take effect on the next sign-in; no restart needed.
B. Your library’s settings.toml. The OIDC settings are library
settings, so they live in
the hidden .photostructure folder inside your library:
oidcIssuerUrl = "https://keycloak.example.com/realms/family"
oidcClientId = "photostructure"
oidcClientSecret = "the-secret-from-your-provider"
C. Environment variables. The same
settings are available as PS_OIDC_ISSUER_URL, PS_OIDC_CLIENT_ID, and
PS_OIDC_CLIENT_SECRET. Handy for Docker.
Setting oidcIssuerUrl and oidcClientId is what turns SSO on. The button
appears as soon as both are set.
Behind a reverse proxy, set
publicBaseUrlto your external address, e.g.https://photos.example.com. This is a system setting (it belongs to the computer, not the library), so it goes in the systemsettings.tomlorPS_PUBLIC_BASE_URL. Without it, PhotoStructure builds its redirect and logout URLs from the incomingHostheader, which a proxy can forward from anywhere. That’s both a broken login and a security hole.
🚪 Step 3: Sign in
Open PhotoStructure’s login page. The “Sign in with SSO” button is now there. Click it, authenticate at your provider, and you’re back in PhotoStructure, signed in.
Who becomes the admin: read this before your first SSO login. The first person to sign in through SSO becomes an admin, no matter what their role claim says. PhotoStructure assumes that whoever set up SSO is the first one through the door. If someone else has access to your provider and logs in first, they get admin.
To be safe, do one of these before opening SSO to everyone:
- Sign in via SSO yourself first, before anyone else can, or
- Create your admin account with a sign-in code or password before enabling SSO. Once any account exists, the “first SSO user is admin” rule no longer applies, and everyone else follows the role rules below.
🎭 Deciding who’s an admin: role mapping
By default, everyone who signs in through SSO for the first time becomes a viewer (read-only). To let your provider decide who’s an admin, editor, or viewer, have it send a role claim (a piece of information about the user, carried in the login token).
Two settings control this:
| Setting | Default | What it does |
|---|---|---|
oidcRoleClaim | "role" | The name of the claim PhotoStructure reads the role from. Its value is matched (case-insensitively) against admin, editor, and viewer. |
oidcDefaultRole | "viewer" | The role used when the claim is missing or isn’t one of the three roles. |
So the recipe is: in your provider, arrange for the intended admins to receive a
claim (say, role) with the value admin, and give everyone else editor or
viewer. Users who arrive with no recognized claim fall back to
oidcDefaultRole. PhotoStructure’s three roles are described on the user
accounts page.
Configure the role claim before anyone logs in. A role claim protects every subsequent SSO user, but it can’t override the first-user-is-admin rule covered above.
🛂 Who’s allowed in: auto-registration
By default (oidcAutoRegister = true), anyone your provider will authenticate
gets a PhotoStructure account automatically on first sign-in, as a viewer
unless a role claim says otherwise. That’s convenient when your provider only
knows your family.
It’s the wrong default if your provider will authenticate a wide audience. If
you point PhotoStructure at, say, Google Workspace where many people have
accounts, set oidcAutoRegister = false. Then only people an admin has already
added on the Manage user accounts page can sign in through SSO; everyone else
is turned away.
🚀 Making SSO the default (or keeping the local form)
If SSO is how everyone signs in, set oidcAutoLaunch = true. Visiting /login
then jumps straight to your provider instead of showing the local code/password
form.
Leave yourself an escape hatch: add ?local=1 to the login URL
(https://your-photostructure-address/login?local=1) to reach the local form
anyway. That’s the way back in if your provider is ever misconfigured or
unreachable. It’s also where PhotoStructure lands you after you log out, so
logging out doesn’t bounce you straight back into SSO.
You can also relabel the button with oidcButtonText (e.g. "Sign in with Google").
🎛️ All the OIDC settings
Every SSO setting, its default, and its environment
variable. All are library
settings except
publicBaseUrl, which is a system setting.
| Setting | Env var | Default | Effect |
|---|---|---|---|
oidcIssuerUrl | PS_OIDC_ISSUER_URL | (unset) | Your provider’s base URL. Setting this and oidcClientId enables SSO. |
oidcClientId | PS_OIDC_CLIENT_ID | (unset) | The client identifier your provider assigned to PhotoStructure. |
oidcClientSecret | PS_OIDC_CLIENT_SECRET | (unset) | The client secret. Leave empty for a public client using PKCE alone. |
oidcScope | PS_OIDC_SCOPE | "openid email profile" | What PhotoStructure asks your provider for. openid is required; email and profile let it create accounts. |
oidcRoleClaim | PS_OIDC_ROLE_CLAIM | "role" | Which claim to read the role from. Matched case-insensitively against the three roles. |
oidcDefaultRole | PS_OIDC_DEFAULT_ROLE | "viewer" | Role for users with no valid role claim. Must be admin, editor, or viewer. |
oidcAutoRegister | PS_OIDC_AUTO_REGISTER | true | Create an account for any user your provider authenticates. Set false to require admin pre-creation. |
oidcAutoLaunch | PS_OIDC_AUTO_LAUNCH | false | Send /login straight to your provider. Bypass with ?local=1. |
oidcButtonText | PS_OIDC_BUTTON_TEXT | "Sign in with SSO" | The label on the SSO button. |
oidcSigningAlgorithm | PS_OIDC_SIGNING_ALGORITHM | "" | Pin the login-token signing algorithm (e.g. RS256). Empty means “use whatever the provider advertises.” |
publicBaseUrl | PS_PUBLIC_BASE_URL | (unset) | System setting. Your canonical external URL, e.g. https://photos.example.com. Set behind a reverse proxy. |
🏢 Provider notes
The details differ by provider, but the shape is always the same: register an
app, set the redirect URL to .../auth/oidc/callback, and copy out the issuer
URL, client ID, and secret. If you’re unsure of the issuer URL, open
<issuer>/.well-known/openid-configuration in a browser. If it returns JSON,
you have the right address.
| Provider | Issuer URL looks like | Notes |
|---|---|---|
| Keycloak | https://keycloak.example.com/realms/<realm> | Create a confidential client; add a mapper that puts a role (or group) claim in the token. |
| Authentik | the OAuth2/OpenID provider’s URL | Create a Provider + Application. Authentik may omit email_verified; PhotoStructure doesn’t require it. |
| Authelia | your Authelia base URL | Register PhotoStructure as an OIDC client in Authelia’s configuration. |
| Google Workspace | https://accounts.google.com | Google authenticates any Google account, so set oidcAutoRegister = false or restrict access. Google rotates signing keys ~daily (see troubleshooting). |
| Microsoft Entra ID | https://login.microsoftonline.com/<tenant-id>/v2.0 | Register an app; the redirect URL is the “Web” platform redirect URI. |
| Okta | https://<your-org>.okta.com | Create an OIDC “Web” application; the issuer may be an authorization-server URL. |
🩺 Troubleshooting
The “Sign in with SSO” button doesn’t appear. Both oidcIssuerUrl and
oidcClientId must be set. If either is blank, SSO stays off.
“redirect_uri mismatch” or “invalid redirect.” The redirect URL registered
at your provider must exactly equal
<your address>/auth/oidc/callback. Behind a reverse proxy, set publicBaseUrl
so PhotoStructure builds the address your provider expects.
I turned on oidcAutoLaunch and now I can’t reach the local login. Add
?local=1: https://your-photostructure-address/login?local=1.
SSO worked yesterday and broke overnight. Some providers (notably Google) rotate their signing keys frequently. PhotoStructure caches your provider’s details, so a rotation can make sign-in fail until the cache refreshes. Restart PhotoStructure to refresh it; changing the issuer URL, client ID, or client secret also forces a refresh.
Everyone comes in as a viewer. Check that your provider actually sends the
claim named in oidcRoleClaim (default role), and that its value is admin,
editor, or viewer (case doesn’t matter). Anyone without a recognized value
gets oidcDefaultRole.
Someone unexpected became admin. That’s the first-SSO-user rule; see step 3. Recover by signing in through another admin (or the direct-database path) and fixing roles on the Manage user accounts page.
🔒 Security notes
- Put SSO behind HTTPS. Login tokens and session cookies cross the network; a reverse proxy terminating HTTPS is the usual way.
- Set
publicBaseUrlbehind a proxy. Without it, PhotoStructure trusts theHostheader to build its redirect and logout URLs, which a proxy can forward from an attacker-controlled value. - PhotoStructure trusts your provider’s email verification. It links an SSO
login to an existing account by email address, trusting that your provider
verified the address. That’s the right call when you run the provider. If your
provider lets strangers register arbitrary email addresses, keep them out with
oidcAutoRegister = falseand pre-create accounts.
🔗 See also
- User accounts and signing in — turning on accounts, roles, and the local (non-SSO) sign-in
- How do I access my library on other computers? — LAN, VPN, and reverse-proxy setups
- Advanced settings — where the system and library
settings.tomlfiles live - Environment variables — configuring PhotoStructure without editing files
