Authentication
Authentication is disabled by default, so the web UI binds to 127.0.0.1 unless you decide
otherwise. To listen beyond localhost, either enable authentication, name the address
yourself (--host, IMMICH_MEMORIES_SERVER__HOST, or a server.host that is not the 0.0.0.0
wildcard), or set server.allow_unauthenticated_lan: true. A bare server.host: 0.0.0.0 in a
config file does not count; the loader ignores it with a warning. In Docker the image passes
--host 0.0.0.0, so the port mapping is the real boundary: the shipped compose file publishes
127.0.0.1:8080:8080.
Anyone who reaches the port can use the app and, through it, your Immich library. They cannot read the saved API key, and they cannot point it at a server of their own: the UI only sends the key to the URL it was saved for, and a new URL needs its key typed in. The UI is single-user, single-replica: workflow state lives in the process, so a shared secret or volume does not make replicas safe.

Basic auth
Two variables, in a .env beside docker-compose.yml or in the service's environment::
IMMICH_MEMORIES_AUTH_USERNAME=admin
IMMICH_MEMORIES_AUTH_PASSWORD=your-password-here
Set both; one alone is ignored, and setting both turns auth on with provider: basic. From a
shell each line needs export in front of it. The YAML form:
advanced:
auth:
enabled: true
provider: basic
username: admin
password: ${MY_SECRET_PASSWORD} # password, client_secret, issuer_url and client_id expand ${VAR}
immich-memories --config PATH ui uses PATH for every decision, authentication included: bind
address, auth, the Immich connection and the settings pages all read the one file, and a reload
reloads that same file.
OIDC / SSO
Any OpenID Connect provider, with PKCE, discovered from issuer_url/.well-known/openid-configuration.
Docker images ship authlib; a pip install needs immich-memories[auth].
advanced:
auth:
enabled: true
provider: oidc
issuer_url: https://your-idp.example.com
client_id: immich-memories
client_secret: ${OIDC_CLIENT_SECRET} # empty for public clients
public_url: https://memories.example.com # the URL users reach you on
allowed_emails: [me@example.com]
allowed_domains: [example.com] # exact: does not admit sub.example.com
auto_launch: false # true: straight to the IdP, no login page
public_url pins the redirect_uri sent to the IdP, so it must match what you registered there,
and it is what makes callback-origin validation possible. Register the callback URL
https://your-host/auth/callback and the logout URL https://your-host/logout. Auth0: a Regular
Web Application. Keycloak: a client with client authentication enabled. Authelia: a client with
pkce_challenge_method: S256, scopes openid, email, profile, grant type
authorization_code. The rest of the auth keys are in the
config reference.
An expired sign-in or a reused callback returns you to the sign-in page with
"Sign-in expired. Try again." Click the SSO button to start a fresh sign-in.
This page waits for your click even with auto_launch: true.
With no allow-list, anyone your IdP authenticates becomes a full admin of this app, Immich API key
included. Fine for a personal tenant; wrong for a shared realm or an Auth0 tenant with social
sign-ups. Set allowed_emails or allowed_domains: either list admits, matching ignores case, and
once either is set the token must carry both email and the boolean email_verified: true.
Configure your provider to verify addresses and include that claim: a matching address the IdP has
not verified is refused, because on a tenant with open sign-ups anyone can type one. A refused
account gets a "Not authorised" page, not a redirect loop.
Trusted header (reverse proxy)
For a proxy that authenticates (Traefik + Authelia, nginx + oauth2-proxy) and forwards the user in a header.
advanced:
auth:
enabled: true
provider: header
user_header: Remote-User # default
email_header: Remote-Email # default, optional
trusted_proxies:
- 172.20.0.2 # your proxy; prefer a /32 over a whole Docker network
session_ttl_hours: 24
Headers are read only when the immediate peer is in trusted_proxies; from anyone else the request
is unauthenticated, and an empty trusted_proxies here fails validation at startup. The app strips
nothing, so a proxy that forwards the header from an untrusted source is your problem to fix.
Authelia forwards Remote-User and Remote-Email by default.
Behind a reverse proxy with TLS
Four things, whichever provider you use:
- Do not publish 8080 on the LAN once the proxy is up: bind it to localhost, or keep the container on the proxy network only.
- List the proxy in
auth.trusted_proxies, so itsX-Forwarded-ForandX-Forwarded-Protoare honoured. Without it every visitor looks like the proxy (five wrong passwords from anyone lock login for everyone for ten minutes) and the OIDCredirect_uriis built ashttp://."*"trusts any peer; use it only when the app is unreachable except through the proxy. - Set
auth.public_urlto the address users type. - Set
server.secure_cookies: trueso the session cookie carriesSecure, but only once every visitor arrives over HTTPS: with it on, a plainhttp://origin other thanlocalhostcannot log in.
FORWARDED_ALLOW_IPS in the environment wins over trusted_proxies when set.
Sessions
A session is a signed cookie and lasts session_ttl_hours (24 by default). The key that signs it
is IMMICH_MEMORIES_STORAGE_SECRET, or else ~/.immich-memories/.storage_secret, generated on
first start, so logins survive a restart; change it and every session ends. Sign out in the
top bar, or /logout, ends the session; OIDC logout also ends the IdP
session when the IdP advertises end_session_endpoint. Scripts authenticate to
the trigger endpoint with
server.trigger_token instead.