---
title: Single sign-on (SSO / OIDC)
url: https://photostructure.com/guide/single-sign-on/
description: How to connect PhotoStructure to your identity provider (Keycloak, Authentik, Authelia, Google Workspace, Microsoft Entra ID, Okta) using OpenID Connect, including role mapping and the redirect URL you have to get right.
date: 2026-07-04
keywords: authentication, OIDC, reverse-proxy, settings, environment-variables
---


{{< note >}} Support for single sign-on and [user
accounts](/guide/user-accounts/) arrive in a PhotoStructure beta build in July
2026, and aren't yet in the current release. {{< /note >}}

If you already run an identity provider (the service that holds everyone's
logins, like [Keycloak](https://www.keycloak.org/),
[Authentik](https://goauthentik.io/), [Authelia](https://www.authelia.com/), 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 {#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](/guide/user-accounts/)
are simpler and need nothing extra to set up.

This works through [OpenID Connect](https://openid.net/developers/how-connect-works/)
(OIDC), the standard layer on top of OAuth2 that nearly every identity provider
speaks. PhotoStructure uses the Authorization Code flow with
[PKCE](https://oauth.net/2/pkce/), so it's safe to run even without a client
secret.

## 🧳 Before you begin {#before-you-begin}

- **Turn on user accounts first.** SSO is layered on top of PhotoStructure's
  [user accounts](/guide/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](/guide/user-accounts/#turning-on-accounts-and-creating-the-first-admin))
  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-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](#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](#step-2-tell-photostructure-about-your-provider)) so PhotoStructure
builds the same URL your provider expects.

## ✍️ Step 1: Register PhotoStructure with your provider {#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:

1. **Issuer URL** — the base address of your provider, e.g.
   `https://keycloak.example.com/realms/family`. PhotoStructure reads
   `<issuer>/.well-known/openid-configuration` from it to discover everything
   else automatically.
2. **Client ID** — the identifier your provider assigns to PhotoStructure.
3. **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`](#the-one-url-you-have-to-get-right) address above.

## 🗣️ Step 2: Tell PhotoStructure about your provider {#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](/getting-started/advanced-settings/#library-settings), so they live in
the hidden `.photostructure` folder inside your library:

```toml
oidcIssuerUrl = "https://keycloak.example.com/realms/family"
oidcClientId = "photostructure"
oidcClientSecret = "the-secret-from-your-provider"
```

**C. [Environment variables](/guide/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 `publicBaseUrl` to your external address, e.g.
> `https://photos.example.com`. This is a
> [system setting](/getting-started/advanced-settings/#system-settings) (it
> belongs to the computer, not the library), so it goes in the _system_
> `settings.toml` or `PS_PUBLIC_BASE_URL`. Without it, PhotoStructure builds its
> redirect and logout URLs from the incoming `Host` header, which a proxy can
> forward from anywhere. That's both a broken login and a security hole.

## 🚪 Step 3: Sign in {#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](/guide/user-accounts/#turning-on-accounts-and-creating-the-first-admin)
>   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 {#deciding-whos-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](/guide/user-accounts/#roles-who-can-do-what).

**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 {#whos-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) {#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 {#all-the-oidc-settings}

Every SSO setting, its default, and its [environment
variable](/guide/environment-variables/). All are [library
settings](/getting-started/advanced-settings/#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 {#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 {#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](#step-3-sign-in). Recover by signing in through another admin (or the
[direct-database path](/guide/user-accounts/#when-someone-forgets-their-password))
and fixing roles on the **Manage user accounts** page.

## 🔒 Security notes {#security-notes}

- **Put SSO behind HTTPS.** Login tokens and session cookies cross the network;
  a [reverse proxy](/guide/remote-access/) terminating HTTPS is the usual way.
- **Set `publicBaseUrl` behind a proxy.** Without it, PhotoStructure trusts the
  `Host` header 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 = false` and pre-create accounts.

## 🔗 See also {#see-also}

- [User accounts and signing in](/guide/user-accounts/) — turning on accounts, roles, and the local (non-SSO) sign-in
- [How do I access my library on other computers?](/guide/remote-access/) — LAN, VPN, and reverse-proxy setups
- [Advanced settings](/getting-started/advanced-settings/) — where the system and library `settings.toml` files live
- [Environment variables](/guide/environment-variables/) — configuring PhotoStructure without editing files

