Authentik OIDC authentication for the admin dashboard #10

Closed
opened 2026-07-07 21:54:35 -06:00 by jhodgkin · 2 comments
Owner

Goal

Replace simple password auth with Authentik OIDC so the photographer logs in with their existing homelab SSO account. Authentik is already running at auth.jerodrigged.com.

Authentik setup (one-time, manual)

  • Create a new OAuth2/OpenID Connect Provider in Authentik
    • Name: lisilou-portfolio
    • Client type: Confidential
    • Redirect URI: https://<portfolio-domain>/api/auth/callback
    • Scopes: openid profile email
  • Create an Application bound to that provider
  • Copy Client ID and Client Secret into api/.env

Dependencies

npm install openid-client express-session connect-sqlite3

API changes

New env vars (api/.env)

OIDC_ISSUER=https://auth.jerodrigged.com/application/o/lisilou-portfolio/
OIDC_CLIENT_ID=<from authentik>
OIDC_CLIENT_SECRET=<from authentik>
OIDC_REDIRECT_URI=https://<domain>/api/auth/callback
SESSION_SECRET=<random 64-char string>

New routes (api/auth.js)

  • GET /api/auth/login — builds authorization URL and redirects to Authentik
  • GET /api/auth/callback — exchanges code for tokens, validates ID token, stores user in session, redirects to /dashboard
  • GET /api/auth/logout — destroys session, redirects to Authentik end_session_endpoint
  • GET /api/auth/me — returns current session user (used by dashboard to check auth state)

Session middleware

  • express-session with connect-sqlite3 store (reuses existing SQLite DB file)
  • Sessions expire after 8 hours
  • All /api/admin/* routes protected by requireAuth middleware that checks req.session.user
  • Unauthenticated requests to /api/admin/* return 401 with { loginUrl: "/api/auth/login" }

nginx change

  • /dashboard → serve src/dashboard.html
  • /api/auth/ → proxy to api service (same as /api/)

Acceptance

  • Visiting /dashboard when not logged in redirects through Authentik
  • After Authentik login, redirected back to /dashboard with active session
  • /api/auth/me returns the logged-in user's name/email
  • Logging out ends the session and returns to the portfolio home page
## Goal Replace simple password auth with Authentik OIDC so the photographer logs in with their existing homelab SSO account. Authentik is already running at `auth.jerodrigged.com`. ## Authentik setup (one-time, manual) - Create a new **OAuth2/OpenID Connect Provider** in Authentik - Name: `lisilou-portfolio` - Client type: Confidential - Redirect URI: `https://<portfolio-domain>/api/auth/callback` - Scopes: `openid profile email` - Create an **Application** bound to that provider - Copy Client ID and Client Secret into `api/.env` ## Dependencies ``` npm install openid-client express-session connect-sqlite3 ``` ## API changes ### New env vars (`api/.env`) ``` OIDC_ISSUER=https://auth.jerodrigged.com/application/o/lisilou-portfolio/ OIDC_CLIENT_ID=<from authentik> OIDC_CLIENT_SECRET=<from authentik> OIDC_REDIRECT_URI=https://<domain>/api/auth/callback SESSION_SECRET=<random 64-char string> ``` ### New routes (`api/auth.js`) - `GET /api/auth/login` — builds authorization URL and redirects to Authentik - `GET /api/auth/callback` — exchanges code for tokens, validates ID token, stores user in session, redirects to `/dashboard` - `GET /api/auth/logout` — destroys session, redirects to Authentik end_session_endpoint - `GET /api/auth/me` — returns current session user (used by dashboard to check auth state) ### Session middleware - `express-session` with `connect-sqlite3` store (reuses existing SQLite DB file) - Sessions expire after 8 hours - All `/api/admin/*` routes protected by `requireAuth` middleware that checks `req.session.user` - Unauthenticated requests to `/api/admin/*` return `401` with `{ loginUrl: "/api/auth/login" }` ### nginx change - `/dashboard` → serve `src/dashboard.html` - `/api/auth/` → proxy to api service (same as `/api/`) ## Acceptance - Visiting `/dashboard` when not logged in redirects through Authentik - After Authentik login, redirected back to `/dashboard` with active session - `/api/auth/me` returns the logged-in user's name/email - Logging out ends the session and returns to the portfolio home page
Author
Owner

Scope expanded: two Authentik groups required — lisilou-photographers (dashboard access) and lisilou-clients (client portal access). Self-registration enrollment flow covered in #12. API must check group claims from the OIDC token to differentiate access levels. requireAdmin middleware for /api/admin/* and requireAuth for /api/client/*.

Scope expanded: two Authentik groups required — `lisilou-photographers` (dashboard access) and `lisilou-clients` (client portal access). Self-registration enrollment flow covered in #12. API must check group claims from the OIDC token to differentiate access levels. requireAdmin middleware for /api/admin/* and requireAuth for /api/client/*.
Author
Owner

Implemented:

  • Created OAuth2/OIDC Provider + Application lisilou-portfolio in Authentik via API (client_type=confidential, implicit-consent flow, per-provider issuer, scope mappings for openid/profile/email). Redirect URIs registered: https://lisilou.com/api/auth/callback, https://dev-lisilou.jerodrigged.com/api/auth/callback (new dev domain, Cloudflare Tunnel -> nginx -> CT114), and http://192.168.1.192:8080/api/auth/callback as a direct-IP fallback.
  • api/auth.js — login/callback/logout/me routes using openid-client v5 (pinned to v5 since v6 is ESM-only and this codebase is CJS).
  • api/session-store.js — custom express-session store backed by the existing better-sqlite3 db (new sessions table), avoiding a second native sqlite dependency (connect-sqlite3 pulls in sqlite3, which fails to build in some environments).
  • requireAdmin in api/server.js now accepts EITHER an OIDC session OR the existing ADMIN_SECRET bearer token, so the current Playwright suite and admin scripts (CSV export, etc.) keep working unchanged.
  • Dashboard login screen: added "Log In with SSO" as the primary option, kept the passphrase field as a fallback. Logout routes through Authentik's end_session_endpoint when the session came from SSO.
  • Verified end-to-end against the live Authentik instance: built the api Docker image, confirmed /api/auth/login redirects to a correctly-formed authorization URL at auth.jerodrigged.com with proper state/nonce/session cookie.

Remaining manual step: OIDC_CLIENT_ID/SECRET/ISSUER/REDIRECT_URI need to be added to api/.env on CT114 (sent to the repo owner directly, not committed).

This unblocks #12 and #13.

Implemented: - Created OAuth2/OIDC Provider + Application `lisilou-portfolio` in Authentik via API (client_type=confidential, implicit-consent flow, per-provider issuer, scope mappings for openid/profile/email). Redirect URIs registered: `https://lisilou.com/api/auth/callback`, `https://dev-lisilou.jerodrigged.com/api/auth/callback` (new dev domain, Cloudflare Tunnel -> nginx -> CT114), and `http://192.168.1.192:8080/api/auth/callback` as a direct-IP fallback. - `api/auth.js` — login/callback/logout/me routes using openid-client v5 (pinned to v5 since v6 is ESM-only and this codebase is CJS). - `api/session-store.js` — custom express-session store backed by the existing better-sqlite3 db (new `sessions` table), avoiding a second native sqlite dependency (connect-sqlite3 pulls in `sqlite3`, which fails to build in some environments). - `requireAdmin` in `api/server.js` now accepts EITHER an OIDC session OR the existing `ADMIN_SECRET` bearer token, so the current Playwright suite and admin scripts (CSV export, etc.) keep working unchanged. - Dashboard login screen: added "Log In with SSO" as the primary option, kept the passphrase field as a fallback. Logout routes through Authentik's end_session_endpoint when the session came from SSO. - Verified end-to-end against the live Authentik instance: built the api Docker image, confirmed `/api/auth/login` redirects to a correctly-formed authorization URL at auth.jerodrigged.com with proper state/nonce/session cookie. Remaining manual step: OIDC_CLIENT_ID/SECRET/ISSUER/REDIRECT_URI need to be added to `api/.env` on CT114 (sent to the repo owner directly, not committed). This unblocks #12 and #13.
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: jhodgkin/lisilou-portfolio#10