49 KiB
Security Implementation Plan: Google OAuth SSO for Homelab Infrastructure
Version: 1.1 (Revised Scope) Date: 2025-11-15 Status: Research & Planning Phase Revision: Focused scope on external-facing services, FastAPI native OIDC for core-api
Executive Summary
This document outlines a comprehensive security architecture to implement Single Sign-On (SSO) across all homelab services using Google OAuth (Google Workspace + Gmail accounts). The proposed solution uses Authentik as a central Identity Provider (IdP) that federates with Google for authentication, then provides OIDC/SAML/LDAP to downstream services.
Critical Security Gap Identified: The core-api service currently has NO authentication, making it a severe security risk if exposed publicly. This must be addressed immediately as part of the SSO implementation.
Key Benefits:
- Single login for all services using Google accounts
- Centralized user management and access control
- Support for both Google Workspace and Gmail accounts
- Eliminates password fatigue and scattered credentials
- Enables secure public exposure of services via api.schweitz.net
- Provides audit trail and access logging
0. Scope Definition & Architecture Decision
0.1 Implementation Scope
This plan focuses only on user-facing services with external domain mappings, plus critical components requiring public access:
✅ IN SCOPE:
- core-api (api.schweitz.net) - CRITICAL: Currently no auth, public exposure needed
- Nextcloud (cloud.schweitz.net) - File storage and collaboration
- Jellyfin (media.schweitz.net) - Media server
- Gitea (git.schweitz.net) - Git hosting
- Open WebUI (ai.schweitz.net) - AI interface
- Organizr (home.schweitz.net) - Unified dashboard
- code-server (code.schweitz.net) - VS Code in browser (host service)
❌ OUT OF SCOPE:
- Infrastructure tools (Portainer, Uptime Kuma, Netdata, Headscale, Watchtower) - Accessible via core-api endpoints if needed, no direct public exposure required
- Data providers (Ollama, Qdrant, future stack backends) - Internal-only services
- Local services (Samba, NPM admin) - LAN-only access
Rationale:
- Infrastructure tools can be proxied through authenticated core-api endpoints when needed
- Reduces implementation complexity and attack surface
- Focuses SSO on end-user services with clear external access requirements
- Maintains security without over-engineering
0.2 Core-API Authentication Strategy: FastAPI Native OIDC
Decision: Implement OIDC token validation directly in FastAPI rather than NPM forward auth.
Advantages:
- Native FastAPI dependency injection (
Depends) - Fine-grained endpoint-level authorization
- Better integration with API documentation (OpenAPI/Swagger)
- No reliance on HTTP headers from proxy
- Standard OAuth2 bearer token authentication
- Easier to test and maintain
Implementation:
from fastapi import Depends, Security, HTTPException
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import jwt
from jwt import PyJWKClient
# OIDC configuration from Authentik
OIDC_ISSUER = "https://auth.schweitz.net/application/o/core-api/"
JWKS_URL = f"{OIDC_ISSUER}/jwks/"
security = HTTPBearer()
jwks_client = PyJWKClient(JWKS_URL)
async def get_current_user(
credentials: HTTPAuthorizationCredentials = Security(security)
) -> dict:
"""Validate OIDC token and return user claims"""
token = credentials.credentials
try:
signing_key = jwks_client.get_signing_key_from_jwt(token)
payload = jwt.decode(
token,
signing_key.key,
algorithms=["RS256"],
audience="core-api",
issuer=OIDC_ISSUER
)
return payload
except jwt.InvalidTokenError as e:
raise HTTPException(status_code=401, detail="Invalid authentication")
# Usage in endpoints
@app.get("/infrastructure/services")
async def get_services(user: dict = Depends(get_current_user)):
# user contains email, name, groups from token
return services
Public Endpoints:
- Health check (
/health) - Unauthenticated - OpenAPI docs (
/docs,/openapi.json) - Unauthenticated (or optionally protected) - Static files (
/static/*) - Protected via NPM forward auth OR require auth token
Protected Endpoints:
- All
/infrastructure/*- Require valid OIDC token - All
/v1/*(AI endpoints) - Require valid OIDC token
1. Current State: Service Inventory & Authentication Capabilities
1.1 In-Scope Services (External-Facing)
| Service | Type | Port | Domain | Current Auth | SSO Support | Integration Method |
|---|---|---|---|---|---|---|
| core-api | API | 8083 | api.schweitz.net | NONE ❌ | ✅ | FastAPI native OIDC |
| Nextcloud | App | 8082 | cloud.schweitz.net | MariaDB | ✅ | OIDC (user_oidc app) |
| Jellyfin | App | 8096 | media.schweitz.net | Built-in | ✅ | OIDC (sso plugin) |
| Gitea | App | 3002 | git.schweitz.net | PostgreSQL | ✅ | OIDC (native) |
| Open WebUI | App | 82 | ai.schweitz.net | Built-in | ✅ | OIDC (native) |
| Organizr | Dashboard | 9999 | home.schweitz.net | Built-in | ⚠️ | Forward auth (NPM) |
| code-server | IDE | 8084 | code.schweitz.net | Password | ⚠️ | Forward auth (NPM) |
Legend:
- ✅ Native OIDC/OAuth support
- ⚠️ Proxy-based authentication (forward auth via NPM)
- ❌ No authentication (critical security gap)
1.2 Out-of-Scope Services
These services remain accessible on local network or via core-api authenticated endpoints:
| Service | Type | Port | Reason Out of Scope |
|---|---|---|---|
| Portainer | Infrastructure | 8080 | Admin tool - accessible via core-api proxy if needed |
| Uptime Kuma | Monitoring | 3001 | Admin tool - accessible via core-api proxy if needed |
| Netdata | Monitoring | 19999 | Admin tool - accessible via core-api proxy if needed |
| Headscale | VPN | 8085 | VPN control plane, no web auth needed |
| NPM | Infrastructure | 8000 | LAN admin access only |
| Ollama | AI Backend | 11434 | Internal API, no direct access |
| Qdrant | Database | 6333 | Internal vector DB, no direct access |
| Samba | File Share | 445 | LAN-only SMB service |
| Watchtower | Automation | - | Background service, no UI |
1.3 Current Security Posture
Strengths:
- Most services isolated behind NPM reverse proxy
- VPN access available via Headscale for infrastructure tools
- Core infrastructure tools not publicly exposed
Critical Weaknesses:
- core-api has ZERO authentication (blocks api.schweitz.net public exposure)
- code-server uses basic password (weak auth for IDE access)
- Password sprawl across 7 different user-facing services
- No centralized authentication or session management
- No unified access control or audit logging
- User management scattered across multiple databases (MariaDB, PostgreSQL, local files)
2. Security Requirements & Goals
2.1 Primary Objectives
-
Google OAuth Integration
- Support Google Workspace accounts (e.g., user@schweitz.net)
- Support Gmail accounts (e.g., user@gmail.com)
- Single source of truth for user identities
-
Centralized Access Control
- Manage user access from one location
- Role-based access control (RBAC) per service
- Easy user onboarding/offboarding
-
Secure Public Access
- Enable safe exposure of core-api at api.schweitz.net
- Protect service control widget and infrastructure APIs
- Maintain security for publicly accessible services
-
Minimal User Friction
- One-click login via Google
- Session management across services
- Mobile-friendly authentication
2.2 Security Standards
- Authentication: OAuth 2.0 / OIDC with Google as IdP
- Authorization: RBAC with service-level granularity
- Session Management: Secure cookie handling, configurable timeouts
- Transport Security: TLS 1.3 via Let's Encrypt (already configured in NPM)
- Audit Logging: Authentication events, access attempts, authorization decisions
3. SSO Solution Evaluation
3.1 Solution Comparison Matrix
| Criteria | Authelia | Authentik | Keycloak |
|---|---|---|---|
| Architecture | Forward auth proxy | Full IdP | Enterprise IdP |
| Resource Usage | Low (~100MB RAM) | Medium (~300MB RAM) | High (1-2GB RAM) |
| Setup Complexity | Simple | Moderate | Complex |
| Google as Auth Source | ❌ NO | ✅ YES | ✅ YES |
| Acts as OIDC Provider | ✅ YES | ✅ YES | ✅ YES |
| LDAP Backend | LDAP only | Multiple | Multiple |
| User Database | File/LDAP | PostgreSQL | PostgreSQL/MySQL |
| UI Quality | Minimal | Modern | Enterprise |
| NPM Integration | Excellent | Good | Good |
| Community Support | Large | Growing | Massive |
| Homelab Suitability | ⚠️ High* | ✅ Excellent | ⚠️ Overkill |
*Authelia is excellent for homelabs, but cannot use Google as the authentication source - it requires its own user database (file-based or LDAP).
3.2 Detailed Analysis
Authelia
Strengths:
- Lightweight and fast
- Perfect NPM integration via forward auth
- Simple configuration (YAML files)
- Low resource usage
- Excellent for homelab scale
Critical Limitation:
- Cannot federate with Google OAuth - only acts as an OIDC provider
- Requires separate user database (LDAP or file-based)
- Would need to manually sync Google users to LDAP
- Does not meet requirement for "login with Google"
Verdict: ❌ Does not meet core requirement (Google as auth source)
Authentik
Strengths:
- Can use Google as authentication source (meets requirement!)
- Modern Python-based architecture
- Beautiful, intuitive UI
- Flow-based configuration (flexible auth/authz journeys)
- Acts as OIDC/SAML provider for downstream services
- Good documentation and active community
- Reasonable resource usage (~300-500MB)
- Docker-native, homelab-friendly
Limitations:
- More complex than Authelia
- Requires PostgreSQL database
- NPM integration via OIDC (not forward auth)
Verdict: ✅ RECOMMENDED - Best fit for requirements
Keycloak
Strengths:
- Battle-tested enterprise solution
- Most comprehensive feature set
- Extensive protocol support
- Can federate with Google
- Massive ecosystem
Limitations:
- Heavy resource usage (1-2GB RAM minimum)
- Complex UI and configuration
- Overkill for homelab scale
- Steeper learning curve
Verdict: ⚠️ Viable but excessive for homelab
3.3 Recommendation: Authentik
Authentik is the optimal choice because:
- Meets Core Requirement: Supports Google OAuth as authentication source
- Right-Sized: Not too simple (Authelia), not too complex (Keycloak)
- Modern Architecture: Python-based, active development, good docs
- Flexible Integration: Can provide OIDC, SAML, LDAP, and proxy auth
- User Experience: Clean UI for both admins and end-users
- Resource Efficient: ~300-500MB RAM is acceptable for homelab
4. Recommended Architecture
4.1 High-Level Design
┌─────────────────────────────────────────────────────────────┐
│ Internet (Public) │
└─────────────────────┬───────────────────────────────────────┘
│
┌────────────▼────────────┐
│ Google OAuth 2.0 │ ← User: "Sign in with Google"
│ (accounts.google.com) │ (Workspace + Gmail accounts)
└────────────┬────────────┘
│ OAuth tokens
┌────────────▼────────────┐
│ Authentik IdP │ ← Central identity provider
│ (auth.schweitz.net) │ - Validates Google auth
│ │ - Manages users & groups
│ PostgreSQL + Redis │ - Issues OIDC tokens & JWTs
└────────┬─────────────────┘
│
│ OIDC tokens / Forward auth
│
┌───────────┼───────────────────────────────────┐
│ │ │
┌─────▼─────┐ ┌──▼────────────┐ ┌────────────▼──────┐
│ NPM │ │ core-api │ │ User Apps (OIDC) │
│ Gateway │ │ (FastAPI) │ │ │
│ │ │ │ │ - Nextcloud │
│ Routes: │ │ Bearer Token │ │ - Jellyfin │
│ *.schweitz │ Validation ────┤ │ - Gitea │
│ .net │ │ (python-jose) │ │ - Open WebUI │
│ │ └───────────────┘ └───────────────────┘
│ │
│ Forward │ ┌───────────────────────────┐
│ Auth ├────────► Proxy Auth Services │
│ (Authent │ │ - Organizr (dashboard) │
│ ik) │ │ - code-server (host) │
│ │ └───────────────────────────┘
└───────────┘
Legend:
━━━ Direct OIDC/OAuth ─── HTTP Proxy ╌╌╌ Forward Auth
4.2 Authentication Flow
Initial Login:
- User visits
home.schweitz.net(or any protected service) - NPM/Service redirects to Authentik:
auth.schweitz.net - Authentik shows "Sign in with Google" button
- User authenticates with Google (Workspace or Gmail)
- Google returns OAuth token to Authentik
- Authentik creates/updates user profile, issues OIDC token
- User redirected back to original service with session cookie
- Service validates OIDC token, grants access
Subsequent Access:
- Session cookie valid for configurable duration (e.g., 8 hours)
- No re-authentication required within session
- Logout clears all service sessions
4.3 Component Architecture
New Components to Deploy
-
Authentik Stack (new)
authentik-server: Main application serverauthentik-worker: Background taskspostgresql: User databaseredis: Cache and message queue- Resources: ~500MB RAM, 1 CPU, 5GB SSD
- Port: 9000 (internal), exposed via NPM
-
NPM Configuration Updates
- Add proxy host:
auth.schweitz.net→authentik-server:9000 - Add forward auth snippet for protected services
- Configure SSL certificates (Let's Encrypt)
- Add proxy host:
Integration Patterns
Pattern A: FastAPI Native OIDC (core-api only)
- Service: core-api
- Integration: Direct JWT validation in FastAPI using python-jose
- Session: Stateless bearer token authentication
- Benefits: Fine-grained endpoint control, API-first security, OpenAPI integration
Pattern B: Native OIDC (Preferred for user apps)
- Services: Nextcloud, Gitea, Jellyfin, Open WebUI
- Integration: Configure OIDC client in Authentik, add OIDC provider in service
- Session: Service manages its own session after OIDC login
- Benefits: Native integration, best UX, full feature support
Pattern C: Forward Auth via NPM (For non-OIDC services)
- Services: Organizr, code-server
- Integration: NPM/Authentik proxy checks auth before forwarding request
- Session: Handled by Authentik via cookies
- Benefits: Works with any service, no code changes needed
5. Service-by-Service Integration Plan
5.1 Tier 1: Critical Infrastructure (Week 1)
5.1.1 Authentik Deployment
Tasks:
- Create
stacks/authentik.ymlwith PostgreSQL + Redis + Authentik - Deploy stack via Portainer
- Access initial setup at
http://localhost:9000 - Configure:
- Admin account
- Email settings (optional)
- Brand customization
Success Criteria:
- Authentik accessible via NPM at
auth.schweitz.net - Admin portal functional
- Health checks passing
5.1.2 Google OAuth Source Configuration
Tasks:
- Create Google Cloud Project (or use existing)
- Enable OAuth consent screen
- App name: "Homelab SSO"
- Support email: admin email
- Scopes:
openid,email,profile - Authorized domains:
schweitz.net
- Create OAuth 2.0 credentials
- Authorized redirect URIs:
https://auth.schweitz.net/source/oauth/callback/google/
- Authorized redirect URIs:
- Configure Google source in Authentik
- Provider: Google
- Client ID: from Google Cloud
- Client Secret: from Google Cloud
- Scopes:
openid email profile
Testing:
- Test login with Google Workspace account
- Test login with Gmail account
- Verify user profile attributes synced
- Confirm logout works
5.1.3 core-api Protection (CRITICAL)
Current Risk: core-api at port 8083 has zero authentication. Cannot expose publicly without SSO.
Approach: FastAPI Native OIDC (OAuth2 Bearer Token)
Implementation:
-
Create Authentik OIDC Provider
- In Authentik: Applications → Create
- Name: "Core API"
- Slug:
core-api - Provider type: OAuth2/OIDC
- Client type:
Confidential - Client ID: (auto-generated, save for later)
- Client Secret: (auto-generated, save securely)
- Redirect URIs:
https://api.schweitz.net/auth/callback(for web flows if needed) - Signing Key: Auto (Authentik default)
- Subject mode:
Based on User's Email - Include claims in ID token: ✅
- Scopes:
openid,email,profile
-
Install Python Dependencies (add to
requirements.txt):PyJWT[crypto]==2.8.0 python-jose[cryptography]==3.3.0 -
Create Authentication Module (
src/auth/oidc.py):from fastapi import Depends, HTTPException, Security from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from jose import jwt, jwk from jose.utils import base64url_decode import httpx from functools import lru_cache from typing import Dict, Optional from src.config import get_settings from src.logging_config import get_logger logger = get_logger(__name__) settings = get_settings() security = HTTPBearer(auto_error=False) # OIDC Configuration OIDC_ISSUER = settings.oidc_issuer # "https://auth.schweitz.net/application/o/core-api/" JWKS_URI = f"{OIDC_ISSUER}jwks/" @lru_cache() def get_jwks() -> Dict: """Fetch JWKS from Authentik (cached)""" try: response = httpx.get(JWKS_URI, timeout=10) response.raise_for_status() return response.json() except Exception as e: logger.error(f"Failed to fetch JWKS: {e}") raise HTTPException(status_code=500, detail="Auth configuration error") async def get_current_user( credentials: Optional[HTTPAuthorizationCredentials] = Security(security) ) -> Dict: """ Validate OIDC token from Authorization: Bearer header Returns: User claims (email, name, groups, etc.) Raises: HTTPException 401 if token invalid/missing """ if not credentials: raise HTTPException( status_code=401, detail="Missing authentication token", headers={"WWW-Authenticate": "Bearer"}, ) token = credentials.credentials try: # Decode header to get key ID unverified_header = jwt.get_unverified_header(token) kid = unverified_header.get("kid") # Find matching key in JWKS jwks = get_jwks() rsa_key = None for key in jwks.get("keys", []): if key.get("kid") == kid: rsa_key = key break if not rsa_key: raise HTTPException(status_code=401, detail="Invalid token key") # Verify and decode token payload = jwt.decode( token, rsa_key, algorithms=["RS256"], audience=settings.oidc_audience, # "core-api" issuer=OIDC_ISSUER, ) logger.info(f"Authenticated user: {payload.get('email')}") return payload except jwt.ExpiredSignatureError: raise HTTPException(status_code=401, detail="Token expired") except jwt.JWTClaimsError: raise HTTPException(status_code=401, detail="Invalid token claims") except Exception as e: logger.error(f"Token validation error: {e}") raise HTTPException(status_code=401, detail="Invalid authentication token") async def get_admin_user(user: Dict = Depends(get_current_user)) -> Dict: """Require admin group membership""" groups = user.get("groups", []) if "admin" not in groups: raise HTTPException(status_code=403, detail="Admin access required") return user -
Update
src/config.py(add OIDC settings):class Settings(BaseSettings): # ... existing settings ... # OIDC Authentication oidc_issuer: str = "https://auth.schweitz.net/application/o/core-api/" oidc_audience: str = "core-api" oidc_enabled: bool = True # Set False to disable auth (dev mode) -
Protect Endpoints (update controllers):
from src.auth.oidc import get_current_user, get_admin_user # Public endpoints (no change needed) @router.get("/health") async def health_check(): return {"status": "healthy"} # Protected endpoints (add dependency) @router.get("/infrastructure/services") async def get_services(user: Dict = Depends(get_current_user)): logger.info(f"User {user['email']} fetching services") return services # Admin-only endpoints @router.post("/infrastructure/services/{name}/stop") async def stop_service(name: str, user: Dict = Depends(get_admin_user)): logger.info(f"Admin {user['email']} stopping {name}") # ... implementation -
Update OpenAPI Documentation (
src/main.py):from fastapi.security import OAuth2AuthorizationCodeBearer oauth2_scheme = OAuth2AuthorizationCodeBearer( authorizationUrl=f"{settings.oidc_issuer}authorize/", tokenUrl=f"{settings.oidc_issuer}token/", scopes={"openid": "OpenID Connect", "email": "Email", "profile": "Profile"} ) app = FastAPI( # ... existing config ... swagger_ui_init_oauth={ "clientId": settings.oidc_client_id, "appName": "Core API", "usePkceWithAuthorizationCodeGrant": True, } ) -
Configure NPM Proxy (simple passthrough):
- Domain:
api.schweitz.net - Forward to:
core-api:8083 - SSL: Let's Encrypt
- No forward auth needed (handled by FastAPI)
- Domain:
-
Update Widget (
static/widgets/service-control.html):// Redirect to Authentik login, then get token async function getAuthToken() { // Check if token in localStorage let token = localStorage.getItem('oidc_token'); if (token && !isTokenExpired(token)) { return token; } // Redirect to Authentik login page const authUrl = 'https://auth.schweitz.net/application/o/core-api/'; window.location.href = authUrl; } // Include token in API calls async function fetchServices() { const token = await getAuthToken(); const response = await fetch(`${API_BASE}/infrastructure/services`, { headers: { 'Authorization': `Bearer ${token}` } }); // ... }
Success Criteria:
- ✅ Unauthenticated requests return 401 with proper error
- ✅ Valid OIDC token grants access to protected endpoints
- ✅ Token validation uses Authentik JWKS
- ✅ User email/groups available in endpoints
- ✅ OpenAPI docs show authentication requirement
- ✅ Widget works with OAuth flow
5.2 Tier 2: User-Facing Services (Week 2)
5.2.1 Nextcloud (OIDC)
Plugin: user_oidc (official Nextcloud app)
Configuration:
- Install app: Apps → Search "OpenID Connect" → Install
- Create Authentik OIDC Provider
- Client type: Confidential
- Redirect URIs:
https://cloud.schweitz.net/apps/user_oidc/code - Scopes:
openid,email,profile
- Configure in Nextcloud
- Settings → OpenID Connect
- Add provider: Authentik
- Discovery URL:
https://auth.schweitz.net/application/o/nextcloud/.well-known/openid-configuration - Client ID: from Authentik
- Client Secret: from Authentik
Testing:
- Verify "Login with Authentik" button appears
- Test new user login creates Nextcloud account
- Test existing Nextcloud users can link Google accounts
- Verify file access preserved after OIDC login
5.2.2 Gitea (OIDC)
Native Support: Gitea has built-in OIDC
Configuration:
- Create Authentik OIDC Provider for Gitea
- Redirect URI:
https://git.schweitz.net/user/oauth2/authentik/callback
- Redirect URI:
- In Gitea Admin → Authentication Sources
- Add authentication source
- Type: OAuth2
- Provider: OpenID Connect
- Client ID: from Authentik
- Client Secret: from Authentik
- Auto Discovery URL:
https://auth.schweitz.net/application/o/gitea/.well-known/openid-configuration
Testing:
- Test new user registration via Google
- Test existing user account linking
- Test SSH key management post-OIDC
- Test repository access
5.2.3 Jellyfin (SSO Plugin)
Plugin: jellyfin-plugin-sso (community plugin)
Configuration:
- Add plugin repository:
- URL:
https://raw.githubusercontent.com/9p4/jellyfin-plugin-sso/manifest-release/manifest.json
- URL:
- Install SSO plugin from catalog
- Create Authentik OIDC Provider for Jellyfin
- Redirect URI:
https://media.schweitz.net/sso/OID/redirect/authentik
- Redirect URI:
- Configure plugin:
- Settings → Plugins → SSO Authentication
- Provider: OIDC
- Client ID/Secret from Authentik
- Discovery URL:
https://auth.schweitz.net/application/o/jellyfin/.well-known/openid-configuration
Testing:
- Test Google login creates Jellyfin user
- Test viewing permissions inherited
- Test continue watching preserved
- Test mobile app compatibility
5.2.4 Open WebUI (Native Google OAuth)
Note: Open WebUI supports Google OAuth directly, but switching to Authentik provides unified management.
Configuration:
- Create Authentik OIDC Provider
- Update
open-webui.ymlenvironment:- ENABLE_OAUTH=true - OAUTH_PROVIDER_NAME=Google (via Authentik) - OPENID_PROVIDER_URL=https://auth.schweitz.net/application/o/open-webui/.well-known/openid-configuration - OAUTH_CLIENT_ID=<from_authentik> - OAUTH_CLIENT_SECRET=<from_authentik> - OAUTH_SCOPES=openid email profile - Restart container
Testing:
- Test login with Google
- Test conversation history preserved
- Test model access unchanged
- Test RAG functionality
5.3 Tier 3: Dashboard & Development Tools (Week 3)
5.3.1 Organizr (Forward Auth)
Note: Organizr doesn't support OIDC natively. Use trusted header authentication.
Configuration:
- Create Authentik Proxy Provider
- External host:
https://home.schweitz.net - Internal host:
http://organizr:80 - Forward auth mode: Single application
- External host:
- Configure NPM with Authentik forward auth snippet
- Organizr settings:
- Settings → System Settings → Authentication
- Enable "Auth Proxy"
- Header name:
X-Authentik-Username - Auto-create users: Yes
Testing:
- Test redirect to Authentik on access
- Test user creation from header
- Test tab access after login
- Test iframe embedding still works
5.3.2 code-server (Forward Auth + Host Service)
Current State: Running as systemd service on host at 127.0.0.1:8084
Note: code-server is a host service (not containerized). Currently uses password authentication.
Approach: Disable built-in auth, use Authentik forward auth via NPM
Configuration:
-
Disable code-server Password Auth
- Edit config:
~/.config/code-server/config.yaml
bind-addr: 127.0.0.1:8084 auth: none # Changed from 'password' cert: false user-data-dir: /home/jpmschweitzer/docker-data/code-server/user-data extensions-dir: /home/jpmschweitzer/docker-data/code-server/extensions- Restart service:
sudo systemctl restart code-server
- Edit config:
-
Create Authentik Proxy Provider
- In Authentik: Applications → Create
- Name: "Code Server"
- Slug:
code-server - Provider type: Proxy Provider
- External host:
https://code.schweitz.net - Internal host:
http://127.0.0.1:8084 - Forward auth mode: Single application
- Authorization flow: (default)
-
Configure NPM Proxy Host
- Domain:
code.schweitz.net - Scheme:
http - Forward Hostname/IP:
192.168.86.149(tower-of-joy IP) - Forward Port:
8084 - SSL: Let's Encrypt
- Websockets: ✅ Enabled (required for VS Code)
- Advanced config (add Authentik forward auth snippet):
# Authentik Forward Auth auth_request /outpost.goauthentik.io/auth/nginx; error_page 401 = @goauthentik_proxy_signin; auth_request_set $auth_cookie $upstream_http_set_cookie; add_header Set-Cookie $auth_cookie; # Pass authentication headers auth_request_set $authentik_username $upstream_http_x_authentik_username; auth_request_set $authentik_groups $upstream_http_x_authentik_groups; auth_request_set $authentik_email $upstream_http_x_authentik_email; auth_request_set $authentik_name $upstream_http_x_authentik_name; auth_request_set $authentik_uid $upstream_http_x_authentik_uid; proxy_set_header X-authentik-username $authentik_username; proxy_set_header X-authentik-groups $authentik_groups; proxy_set_header X-authentik-email $authentik_email; proxy_set_header X-authentik-name $authentik_name; proxy_set_header X-authentik-uid $authentik_uid; location @goauthentik_proxy_signin { internal; add_header Set-Cookie $auth_cookie; return 302 /outpost.goauthentik.io/start?rd=$request_uri; } location /outpost.goauthentik.io { proxy_pass https://auth.schweitz.net/outpost.goauthentik.io; proxy_set_header Host $host; proxy_set_header X-Original-URL $scheme://$http_host$request_uri; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $http_host; proxy_set_header X-Forwarded-Uri $request_uri; proxy_set_header X-Forwarded-Ssl on; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Real-IP $remote_addr; proxy_pass_request_body off; proxy_set_header Content-Length ""; } - Domain:
-
Deploy Authentik Outpost (if not already deployed)
- In Authentik: Outposts → Create
- Name: "NPM Proxy Outpost"
- Type: Proxy
- Integration: Docker (or manual)
- Applications: Select "Code Server" and "Organizr"
Testing:
- ✅ Access
https://code.schweitz.netredirects to Authentik login - ✅ After Google authentication, VS Code loads
- ✅ Workspace and extensions preserved
- ✅ Terminal access works
- ✅ File editing and Git integration functional
- ✅ Websocket connection stable (check browser console)
- ✅ Session persists across page reloads
Security Notes:
- code-server has NO auth when accessed via
http://localhost:8084on the host - Ensure host firewall blocks external access to port 8084
- Only accessible via
code.schweitz.netthrough authenticated NPM proxy - UFW rule:
sudo ufw deny 8084/tcp(block external access)
6. Security Considerations
6.1 Authentication Security
Google OAuth Security:
- OAuth 2.0 with PKCE (Proof Key for Code Exchange)
- Tokens expire per Google's policy (typically 1 hour)
- Refresh tokens handled by Authentik
- No passwords stored in homelab
Session Management:
- Configurable session timeout (recommended: 8 hours)
- Secure, HttpOnly, SameSite cookies
- Session invalidation on logout
- Concurrent session limits (optional)
Multi-Factor Authentication:
- Enforced at Google level (Google Workspace admin can require 2FA)
- Optional: Enable Authentik 2FA as second layer
- Supports TOTP, WebAuthn, Duo
6.2 Authorization & Access Control
Role-Based Access Control (RBAC):
- Define groups in Authentik (e.g.,
admin,family,guest) - Map Google Workspace groups to Authentik groups
- Per-service authorization policies
- Attribute-based access control (ABAC) for advanced scenarios
Service-Level Permissions:
- Portainer: Admin vs User roles
- Nextcloud: File permissions via groups
- Jellyfin: Library access per user
- Gitea: Repository permissions
6.3 Network Security
Public Exposure:
- Only NPM and Authentik accessible from internet
- All other services behind reverse proxy
- Optional: Firewall rules to restrict by geo/IP
- Rate limiting on authentication endpoints
Internal Security:
- Services communicate via Docker networks
- core-api only accessible via authenticated NPM proxy
- Ollama/Qdrant remain internal-only (no public access)
SSL/TLS:
- Let's Encrypt certificates via NPM
- TLS 1.3 minimum
- HSTS headers enabled
- Certificate auto-renewal
6.4 Data Privacy
User Data:
- Email and profile from Google (read-only)
- No passwords stored
- User attributes cached in Authentik database
- GDPR compliance: Users can request data deletion
Logging & Auditing:
- Authentication attempts logged
- Failed login alerts
- Access logs per service
- Retention policy (recommend 90 days)
Secrets Management:
- Client secrets stored in Authentik database (encrypted at rest)
- Database backups encrypted
- Secrets not committed to git
- Consider: HashiCorp Vault for advanced scenarios
6.5 Backup & Recovery
Critical Data to Backup:
- Authentik PostgreSQL database (user profiles, OIDC clients)
- Authentik configuration (flows, policies, providers)
- NPM configuration (proxy hosts, SSL certs)
- Service-specific user databases (if not using OIDC exclusively)
Disaster Recovery:
- Document OIDC client configurations for each service
- Export Authentik flows and policies
- Keep Google OAuth credentials in secure vault
- Test restore procedure quarterly
7. Implementation Phases
Phase 1: Foundation (Week 1)
Goal: Deploy Authentik, configure Google OAuth, protect core-api
Tasks:
- Deploy Authentik stack (server, worker, PostgreSQL, Redis)
- Configure NPM proxy:
auth.schweitz.net - Set up Google Cloud OAuth credentials
- Configure Google as Authentik source
- Test login with Google Workspace account
- Test login with Gmail account
- Create core-api proxy provider
- Configure NPM forward auth for
api.schweitz.net - Test widget access with authentication
- Document OIDC client creation process
Success Criteria:
- ✅ Authentik accessible and functional
- ✅ Google login works for both account types
- ✅ core-api protected and publicly accessible
- ✅ Service control widget requires auth
Rollback Plan:
- Remove
api.schweitz.netNPM proxy - Keep core-api on internal port only
- Destroy Authentik stack
- No impact on existing services
Phase 2: User Services (Week 2)
Goal: Integrate user-facing applications (Nextcloud, Gitea, Jellyfin, Open WebUI)
Tasks:
- Install Nextcloud OIDC app
- Create Authentik OIDC provider for Nextcloud
- Configure and test Nextcloud SSO
- Configure Gitea OIDC authentication
- Test Gitea login and repository access
- Install Jellyfin SSO plugin
- Configure Jellyfin OIDC
- Test Jellyfin media playback with SSO
- Update Open WebUI environment for Authentik
- Test Open WebUI AI conversations with SSO
Success Criteria:
- ✅ All 4 services support Google login
- ✅ New users auto-created on first login
- ✅ Service functionality unchanged post-SSO
- ✅ Sessions persist appropriately
Rollback Plan:
- Disable OIDC providers in Authentik
- Re-enable native auth in each service
- Users revert to local passwords
- No data loss
Phase 3: Admin Tools (Week 3)
Goal: Secure admin interfaces (Portainer, Organizr, Uptime Kuma, Netdata)
Tasks:
- Configure Portainer OAuth
- Test Portainer admin access via Google
- Set up Organizr trusted header auth
- Configure NPM forward auth for Organizr
- Test Organizr tab functionality
- Disable Uptime Kuma internal auth
- Configure forward auth for status.schweitz.net
- Test monitor creation and access
- Configure Netdata forward auth
- Test metrics access and real-time graphs
Success Criteria:
- ✅ All admin tools require Google authentication
- ✅ Functionality preserved
- ✅ Unauthorized access blocked
Rollback Plan:
- Re-enable native auth in services
- Remove forward auth from NPM
- Services remain accessible on local network
Phase 4: Hardening & Documentation (Week 4)
Goal: Finalize security, monitoring, and documentation
Tasks:
- Configure Authentik flows (MFA, password policies)
- Set up Authentik groups (admin, family, guest)
- Define per-service authorization policies
- Enable audit logging across all services
- Set up alerts for failed auth attempts
- Configure session timeouts
- Test logout across all services
- Document user onboarding process
- Create troubleshooting guide
- Backup Authentik configuration
- Test disaster recovery procedure
Success Criteria:
- ✅ Security policies enforced
- ✅ Monitoring and alerting operational
- ✅ Documentation complete
- ✅ Backup/restore tested
8. Alternative Approaches
8.1 Authelia + LDAP (Google Workspace Directory Sync)
Architecture:
- Use Google Workspace Directory Sync or third-party tool
- Sync Google users to OpenLDAP or lldap
- Authelia authenticates against LDAP
- Services use Authelia for forward auth or LDAP directly
Pros:
- Authelia is lighter weight than Authentik
- Simple reverse proxy integration
- Lower resource usage
Cons:
- Cannot directly "log in with Google" (not true SSO)
- Requires separate sync mechanism (complexity)
- Password management still needed (defeats purpose)
- No single-click login experience
- User changes not real-time
Verdict: ❌ Rejected - Doesn't meet "login with Google" requirement
8.2 Keycloak
Pros:
- Enterprise-grade, battle-tested
- Comprehensive features
- Excellent Google federation
Cons:
- Heavy resource usage (1-2GB RAM)
- Overkill for homelab scale
- Complex configuration
Verdict: ⚠️ Viable but excessive
8.3 Service-by-Service Google OAuth
Architecture:
- Configure Google OAuth directly in each service
- No central IdP
- Each service manages its own sessions
Pros:
- No additional infrastructure
- Direct Google integration
Cons:
- No centralized access control
- Each service needs separate OAuth client
- Inconsistent user experience
- Cannot protect services without native OAuth (core-api, Organizr, etc.)
- No unified session management
- User management scattered
Verdict: ❌ Rejected - No solution for core-api or non-OIDC services
9. Cost Analysis
9.1 Resource Requirements
New Infrastructure:
| Component | CPU | RAM | Storage | Notes |
|---|---|---|---|---|
| Authentik Server | 0.5 | 256 MB | 1 GB | Main application |
| Authentik Worker | 0.3 | 128 MB | - | Background tasks |
| PostgreSQL | 0.5 | 256 MB | 2 GB | User database |
| Redis | 0.2 | 64 MB | 100 MB | Cache |
| Total | 1.5 CPU | ~700 MB | ~3 GB |
Current System:
- CPU: Intel i7-6700 (4 cores / 8 threads) - 1.5 core usage is ~19%
- RAM: 16 GB total - 700 MB is ~4%
- Storage: SSD space available
Verdict: ✅ Easily within capacity
9.2 Time Investment
| Phase | Estimated Time | Skill Level |
|---|---|---|
| Research & Planning | 8 hours | All levels |
| Authentik Deployment | 2 hours | Intermediate |
| Google OAuth Setup | 1 hour | Beginner |
| core-api Protection | 2 hours | Intermediate |
| Service Integration (4 services) | 8 hours | Intermediate |
| Admin Tool Integration (4 services) | 6 hours | Intermediate |
| Testing & Validation | 4 hours | All levels |
| Documentation | 4 hours | All levels |
| Total | 35 hours |
Breakdown per week:
- Week 1: 10 hours (Foundation)
- Week 2: 10 hours (User services)
- Week 3: 10 hours (Admin tools)
- Week 4: 5 hours (Hardening)
9.3 Monetary Costs
Required:
- Google Cloud Project: Free (OAuth is free tier)
- Domain: schweitz.net (assuming already owned)
- SSL Certificates: Free (Let's Encrypt via NPM)
Optional:
- Google Workspace subscription: If using Workspace features (not required for OAuth)
- Cloud monitoring tools: Free tier available
Total additional cost: $0 (assuming domain already owned)
10. Risk Assessment
10.1 Technical Risks
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| Authentik failure locks out all services | Low | High | Keep local admin account; VPN access |
| Google OAuth outage | Low | Medium | Authentik has local account fallback |
| Session handling bugs | Medium | Low | Extensive testing; gradual rollout |
| Plugin incompatibilities (Jellyfin, Nextcloud) | Medium | Medium | Test thoroughly; keep native auth during transition |
| Database corruption (PostgreSQL) | Low | High | Automated backups; replication (optional) |
10.2 Operational Risks
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| User confusion during transition | High | Low | Clear documentation; email notifications |
| Lost access due to forgotten Google account | Low | Medium | Admin can manually link accounts |
| Mobile app compatibility issues | Medium | Medium | Test all mobile apps; document workarounds |
| Breaking change in Authentik update | Low | Medium | Pin versions; test updates in staging |
10.3 Security Risks
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| Compromise of Google account | Low | High | Enforce 2FA at Google level |
| Authentik vulnerability | Low | High | Keep updated; subscribe to security advisories |
| Session hijacking | Low | Medium | Secure cookies; short timeouts |
| Misconfigured OIDC client | Medium | Medium | Follow official docs; peer review configs |
11. Success Metrics
11.1 Technical Metrics
- Authentication Success Rate: >99% of login attempts succeed
- Session Uptime: Authentik availability >99.9%
- Response Time: Login flow completes in <3 seconds
- Integration Coverage: 100% of user-facing services support SSO
11.2 Security Metrics
- Zero Unauthorized Access: No successful unauthorized access attempts
- Audit Log Completeness: 100% of auth events logged
- MFA Adoption: >80% of users enable 2FA at Google level (if enforced)
- Failed Login Rate: <5% of attempts fail (indicates good UX)
11.3 User Experience Metrics
- Login Friction: 1 click to authenticate (vs 8+ passwords previously)
- User Onboarding: New user can access all services in <5 minutes
- User Satisfaction: Positive feedback on unified login
- Support Tickets: <10% increase in auth-related support (temporary during transition)
12. Next Steps & Discussion Points
12.1 Questions for User
Before proceeding with implementation, please confirm:
-
Google Account Type:
- Do you have Google Workspace, or Gmail only?
- Should we support both types of accounts?
-
User Base:
- How many users will access the system?
- Do you need group-based access control (e.g., family, friends, admin)?
-
Service Priority:
- Which services are most critical to secure first?
- Any services you want to exclude from SSO?
-
MFA Requirements:
- Should we enforce 2FA for all users?
- At Google level, Authentik level, or both?
-
Session Duration:
- How long should sessions last before re-authentication?
- Different timeouts for admin vs regular users?
-
Rollout Strategy:
- Gradual rollout (one service at a time) or big-bang?
- Pilot with single user before full deployment?
12.2 Immediate Next Actions
If approved, the first concrete steps:
- Create
stacks/authentik.yml- Docker Compose for Authentik stack - Deploy Authentik -
make deploy-authentik(or via Portainer UI) - Access Setup -
http://localhost:9000→ complete initial wizard - Google Cloud Console - Create OAuth 2.0 client credentials
- Configure Google Source - In Authentik admin panel
- Test Authentication - Verify Google login works for test account
- Document OIDC Config - Template for service integration
12.3 Documentation Deliverables
Post-implementation, we'll create:
- User Guide: "How to Log In to Homelab Services"
- Admin Guide: "Managing Users and Access in Authentik"
- Troubleshooting Guide: Common issues and solutions
- Architecture Diagram: Visual representation of auth flow
- Runbook: Disaster recovery and maintenance procedures
13. Conclusion
13.1 Summary
This plan proposes using Authentik as a central Identity Provider (IdP) that federates with Google OAuth to provide secure, unified authentication across all homelab services. This architecture:
- Meets Requirements: Direct Google login for Workspace and Gmail accounts
- Addresses Security Gap: Protects core-api for public API exposure
- Provides Flexibility: Supports OIDC, SAML, LDAP, and proxy auth
- Scales Appropriately: Right-sized for homelab (~700MB RAM)
- Maintains Simplicity: Clear integration patterns per service
13.2 Key Benefits
For Users:
- Single-click login with Google across all services
- No password management or sprawl
- Consistent authentication experience
- Mobile-friendly (Google OAuth optimized)
For Admin:
- Centralized user and access management
- Unified audit logging and security monitoring
- Granular access control per service
- Easy onboarding/offboarding
For Security:
- No passwords stored in homelab
- MFA enforced at Google level
- Reduced attack surface (one auth point)
- Professional-grade OAuth 2.0 implementation
13.3 Recommendation
Proceed with Authentik implementation following the phased approach:
- Week 1: Foundation (Authentik + core-api protection)
- Week 2: User services integration
- Week 3: Admin tools integration
- Week 4: Hardening and documentation
This gradual rollout minimizes risk, allows for testing at each stage, and provides clear rollback points.
Appendix A: Reference Links
Official Documentation
- Authentik: https://docs.goauthentik.io/
- Google OAuth 2.0: https://developers.google.com/identity/protocols/oauth2
- Nginx Proxy Manager: https://nginxproxymanager.com/
- Portainer OAuth: https://docs.portainer.io/admin/settings/authentication/oauth
- Nextcloud OIDC: https://github.com/nextcloud/user_oidc
- Gitea OAuth: https://docs.gitea.com/usage/authentication
- Jellyfin SSO Plugin: https://github.com/9p4/jellyfin-plugin-sso
- Open WebUI SSO: https://docs.openwebui.com/features/auth/sso/
Integration Guides
- Authentik + NPM: https://docs.goauthentik.io/integrations/services/nginx-proxy-manager/
- Authentik + Google: https://docs.goauthentik.io/docs/users-sources/sources/social-logins/google/
- Authentik + Nextcloud: https://docs.goauthentik.io/integrations/services/nextcloud/
- Authentik + Portainer: https://docs.goauthentik.io/integrations/services/portainer/
- Authentik + Gitea: https://www.authelia.com/integration/openid-connect/clients/gitea/ (similar for Authentik)
Community Resources
- r/selfhosted: https://reddit.com/r/selfhosted
- Authentik Discord: https://discord.gg/jg33eMhnj6
- Homelab Forum: https://homelabos.com/
Appendix B: Glossary
- OAuth 2.0: Authorization framework for delegated access
- OIDC (OpenID Connect): Authentication layer on top of OAuth 2.0
- IdP (Identity Provider): Service that authenticates users (Authentik in our case)
- SSO (Single Sign-On): One login for multiple applications
- SAML: XML-based standard for exchanging auth data
- LDAP: Protocol for accessing directory services
- Forward Auth: Reverse proxy checks auth before forwarding request
- PKCE: Security extension for OAuth to prevent interception attacks
- RBAC: Role-Based Access Control
- 2FA/MFA: Two-Factor / Multi-Factor Authentication
End of Document
This plan is ready for review and discussion. Once approved, we can proceed with Phase 1 implementation.