Index: src/config/google.js
===================================================================
--- src/config/google.js	(revision 221a2097e364410e12aa34d7c668aab224bd16b4)
+++ src/config/google.js	(revision 2248cd205170dcacc008a9a13c91cb9cbd894974)
@@ -1,17 +1,14 @@
 // Google OAuth2 voor LUISTERAARS (reageren). Per-instance: de self-hoster zet
-// z'n EIGEN Google-client in .env. Zo hangt elke site aan z'n eigen Google Cloud
-// project — geen centrale afhankelijkheid, geen gedeelde aansprakelijkheid.
+// z'n EIGEN Google-client. Zo hangt elke site aan z'n eigen Google Cloud project
+// — geen centrale afhankelijkheid, geen gedeelde aansprakelijkheid.
 //
-// Optioneel: staat dit niet ingesteld, dan is er simpelweg geen "Login met
-// Google"-knop en blijft de rest van de site werken. Google-login geeft NOOIT
-// beheerrechten — beheer gaat via het wachtwoord-account.
+// Config-bron (in deze volgorde): app_settings (ingesteld via Beheer → Instellingen),
+// anders de env-vars. Niet ingesteld → geen "Login met Google"-knop; de rest van
+// de site werkt door. Google-login geeft NOOIT beheerrechten.
 //
-// Config via env:
-//   GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET
-//   GOOGLE_REDIRECT_URI = https://<dit-domein>/auth/google/callback
+// De redirect-URI wordt afgeleid van PUBLIC_BASE_URL (<base>/auth/google/callback),
+// of expliciet via GOOGLE_REDIRECT_URI. Die exacte URL moet in Google Cloud staan.
 
-const CLIENT_ID = process.env.GOOGLE_CLIENT_ID || '';
-const CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET || '';
-const REDIRECT_URI = process.env.GOOGLE_REDIRECT_URI || '';
+import { getSetting } from '../services/SettingsService.js';
 
 const AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth';
@@ -19,12 +16,27 @@
 const USERINFO_URL = 'https://openidconnect.googleapis.com/v1/userinfo';
 
+// Dynamisch lezen (UI-wijziging werkt zonder herstart). app_settings wint, env = fallback.
+function clientId() {
+  return getSetting('google_client_id', '') || process.env.GOOGLE_CLIENT_ID || '';
+}
+function clientSecret() {
+  return getSetting('google_client_secret', '') || process.env.GOOGLE_CLIENT_SECRET || '';
+}
+export function redirectUri() {
+  if (process.env.GOOGLE_REDIRECT_URI) return process.env.GOOGLE_REDIRECT_URI;
+  const base = (process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, '');
+  return base ? `${base}/auth/google/callback` : '';
+}
+
+export function currentClientId() { return clientId(); } // niet-geheim, voor het formulier
+export function clientSecretSet() { return !!clientSecret(); }
 export function googleConfigured() {
-  return !!(CLIENT_ID && CLIENT_SECRET && REDIRECT_URI);
+  return !!(clientId() && clientSecret() && redirectUri());
 }
 
 export function authorizeUrl(state) {
   const p = new URLSearchParams({
-    client_id: CLIENT_ID,
-    redirect_uri: REDIRECT_URI,
+    client_id: clientId(),
+    redirect_uri: redirectUri(),
     response_type: 'code',
     scope: 'openid email profile',
@@ -39,7 +51,7 @@
   const body = new URLSearchParams({
     code,
-    client_id: CLIENT_ID,
-    client_secret: CLIENT_SECRET,
-    redirect_uri: REDIRECT_URI,
+    client_id: clientId(),
+    client_secret: clientSecret(),
+    redirect_uri: redirectUri(),
     grant_type: 'authorization_code',
   });
Index: src/routes/admin-settings.js
===================================================================
--- src/routes/admin-settings.js	(revision 221a2097e364410e12aa34d7c668aab224bd16b4)
+++ src/routes/admin-settings.js	(revision 2248cd205170dcacc008a9a13c91cb9cbd894974)
@@ -23,4 +23,5 @@
 import { getTenancy, setTenancy, getSetting, setSetting } from '../services/SettingsService.js';
 import { entitlementStatus, premiumUnlocked } from '../services/PatreonService.js';
+import { googleConfigured, redirectUri, currentClientId, clientSecretSet } from '../config/google.js';
 
 const router = express.Router();
@@ -76,4 +77,10 @@
     hubHeroOverlay: clampOverlay(getSetting('hub_hero_overlay')),
     premium: entitlementStatus(),
+    google: {
+      configured: googleConfigured(),
+      redirectUri: redirectUri(),
+      clientId: currentClientId(),
+      secretSet: clientSecretSet(),
+    },
     success: req.query.success || null,
     error: req.query.error || null,
@@ -129,3 +136,18 @@
 });
 
+// Google-login (luisteraars) configureren — Client ID + Secret in app_settings.
+// De redirect-URI leiden we af van PUBLIC_BASE_URL (zie config/google.js).
+router.post('/google', requireGod, (req, res) => {
+  if (req.body.clear === '1') {
+    setSetting('google_client_id', '');
+    setSetting('google_client_secret', '');
+    return res.redirect('/admin/settings?success=' + encodeURIComponent('Google-login losgekoppeld'));
+  }
+  setSetting('google_client_id', (req.body.google_client_id || '').toString().trim());
+  // Secret alleen overschrijven als er een nieuwe waarde is ingevoerd (leeg = laat staan).
+  const secret = (req.body.google_client_secret || '').toString().trim();
+  if (secret) setSetting('google_client_secret', secret);
+  res.redirect('/admin/settings?success=' + encodeURIComponent('Google-login opgeslagen'));
+});
+
 export default router;
Index: src/views/pages/admin-settings.ejs
===================================================================
--- src/views/pages/admin-settings.ejs	(revision 221a2097e364410e12aa34d7c668aab224bd16b4)
+++ src/views/pages/admin-settings.ejs	(revision 2248cd205170dcacc008a9a13c91cb9cbd894974)
@@ -63,4 +63,41 @@
   </section>
   <% } %>
+
+  <%# Google-login voor luisteraars (reageren) — per-site eigen Google-client. %>
+  <section class="set-card" style="margin-top:1rem">
+    <h2>Google-login (luisteraars)</h2>
+    <p class="set-help">Laat bezoekers met Google inloggen om te reageren — met je <strong>eigen</strong>
+      Google Cloud-client. Maak er een aan op
+      <a href="https://console.cloud.google.com/apis/credentials" target="_blank" rel="noopener">console.cloud.google.com</a>
+      (OAuth client-ID, type "Web application").
+      <% if (google && google.configured) { %><strong>Status: ingesteld&nbsp;✓</strong><% } else { %>Nog niet ingesteld.<% } %></p>
+
+    <% if (google && google.redirectUri) { %>
+      <p class="set-help" style="margin-bottom:.7rem">Zet deze <strong>exacte</strong> redirect-URI in je Google-client
+        (Authorized redirect URIs):<br><code style="user-select:all"><%= google.redirectUri %></code></p>
+    <% } else { %>
+      <p class="set-help" style="color:var(--accent)">Stel eerst <code>PUBLIC_BASE_URL</code> in — daar wordt de redirect-URI van afgeleid.</p>
+    <% } %>
+
+    <form method="post" action="/admin/settings/google" class="set-form">
+      <label class="set-field">
+        <span>Client ID</span>
+        <input type="text" name="google_client_id" value="<%= google ? google.clientId : '' %>" placeholder="123-abc.apps.googleusercontent.com" autocomplete="off">
+      </label>
+      <label class="set-field">
+        <span>Client Secret <% if (google && google.secretSet) { %><small style="font-weight:400">— ingesteld; leeg laten = ongewijzigd</small><% } %></span>
+        <input type="password" name="google_client_secret" placeholder="<%= (google && google.secretSet) ? '•••••••• (ingesteld)' : 'GOCSPX-…' %>" autocomplete="off">
+      </label>
+      <div style="display:flex; gap:.5rem; flex-wrap:wrap">
+        <button type="submit" class="btn btn-primary">Opslaan</button>
+        <% if (google && (google.clientId || google.secretSet)) { %>
+          <button type="submit" name="clear" value="1" class="btn">Loskoppelen</button>
+        <% } %>
+      </div>
+    </form>
+    <% if (typeof premiumEnabled !== 'undefined' && premiumEnabled && !(premium && premium.premium)) { %>
+      <p class="set-help" style="margin-top:.7rem; color:var(--accent)">Premium-laag staat aan zonder Patreon — de Google-knop blijft verborgen tot premium ontgrendeld is.</p>
+    <% } %>
+  </section>
 
   <% if (tenancy === 'hub') { %>
