Implements configurable Audible region selection in setup and admin settings, affecting all Audible API calls and triggering data refresh on change. Adds a user-facing 'Change Password' modal in the header for local users, moving password change from admin-only to all local users via a new /api/auth/change-password endpoint. Updates documentation, API routes, and context to support these features, and removes the old admin-only password change flow.
10 KiB
Authentication Service
Status: ✅ Implemented | Plex OAuth + OIDC + Plex Home + Local Admin + JWT + RBAC
Handles authentication and authorization: Multiple auth providers (Plex OAuth, OIDC, Local Admin), Plex Home profile support, JWT session management, comprehensive access control, role-based authorization.
Authentication: Plex OAuth
- No password management needed
- Users already have Plex accounts
- Seamless integration
- Automatic profile pictures/metadata
- Plex Home support: Each profile = separate user
Session Management: JWT Tokens
- Stateless authentication
- No server-side session storage
- Easy horizontal scaling
- Includes user claims (role, permissions)
Access Control: RBAC
Roles:
- user - Request audiobooks, view own requests, search
- admin - Full system access (settings, users, all requests)
OAuth Flow (with Plex Home Support)
- User clicks "Login with Plex"
- Redirect to Plex OAuth
- User authorizes app
- Redirect back with PIN code
- Exchange code for main account token
- Get main account user info
- Verify user has access to configured Plex server (uses stored machineIdentifier from config)
- Check for Plex Home profiles:
- If profiles exist → Redirect to profile selection page
- If no profiles → Continue with main account
- Profile Selection (if applicable):
- User selects profile from grid
- Enter PIN if profile is protected
- Switch to profile, get profile's auth token
- Create/update user in DB (with profile details)
- Generate JWT
- Return JWT to client
- Client includes JWT in subsequent requests
OAuth Endpoints
GET /api/auth/plex/login - Redirect to Plex OAuth
GET /api/auth/plex/callback?pinId=... - Exchange PIN, check for profiles, return JWT or redirect to profile selection
GET /api/auth/plex/home-users - Get list of Plex Home profiles (requires X-Plex-Token header)
POST /api/auth/plex/switch-profile - Switch to selected profile and complete authentication
POST /api/auth/refresh - Get new access token (refresh token in header)
POST /api/auth/logout - Clear client-side token
GET /api/auth/me - Get current user (JWT in header), includes authProvider field ('plex' | 'oidc' | 'local')
JWT Structure
Access Token (1hr):
{
"sub": "user-uuid",
"plexId": "plex-user-id",
"username": "john_doe",
"role": "admin",
"iat": 1234567890,
"exp": 1234571490
}
Refresh Token (7d):
{
"sub": "user-uuid",
"type": "refresh",
"iat": 1234567890,
"exp": 1234971490
}
Storage:
- Access: HTTP-only cookie + localStorage
- Refresh: HTTP-only secure cookie only
- SameSite=Strict (CSRF protection)
Middleware
requireAuth() - Verifies JWT exists/valid, adds user to request, returns 401 if invalid
requireAdmin() - Checks user.role === 'admin', returns 403 if not, chains after requireAuth
First User Setup
- First user created during setup automatically promoted to admin
- Marked as "setup admin" with
isSetupAdmin=trueflag - Setup admin role is protected - cannot be changed to prevent lockout
- Ensures someone always has admin access after fresh install
- Subsequent users default to 'user' role
- Local admin uses username/password authentication (stored in
authTokenfield as bcrypt hash) plexIdformat:local-{username}for local admin accounts
Local Admin Authentication
Local Admin (Setup Admin):
- Created during setup wizard (step 2)
- Username/password authentication (separate from Plex OAuth)
- Password hashed with bcrypt (10 rounds) and stored in
authTokenfield - Login: POST
/api/auth/admin/loginwith username/password - Identified by:
isSetupAdmin=trueANDplexIdstarts withlocal-
Password Management:
- POST
/api/auth/change-password- Change password for any local user - Requires: current password, new password (min 8 chars), confirmation
- Security: Only accessible to users with
authProvider='local'(Plex/OIDC users cannot change passwords) - Validates current password before allowing change
- Properly decrypts stored password hash before bcrypt comparison
- Available to all local users (setup admin, locally registered users)
Plex Home Profile Support
Overview:
- Plex Home accounts can have multiple profiles (managed users, family members)
- Each profile has its own library ratings, watch history, restrictions
- Architecture: Each profile = separate user in ReadMeABook system
Profile Selection Flow:
- User authenticates with main Plex account
- System fetches list of profiles via
GET https://plex.tv/api/home/users - If profiles exist → Show profile selection page (
/auth/select-profile) - User selects their profile (enters PIN if protected)
- System switches to profile via
POST https://plex.tv/api/home/users/{id}/switch - Profile's auth token is stored (encrypted)
- User record created with profile's details
Profile Data Storage:
plexId: Profile's unique ID (not main account ID)plexUsername: Profile's friendlyNameauthToken: Profile's auth token (encrypted)avatarUrl: Profile's avatarplexHomeUserId: Profile ID for reference (null = main account, set = home profile)
User Isolation:
- Each profile is a completely separate user
- Separate requests, separate BookDate recommendations, separate ratings
- Admin sees all profiles as independent users (no grouping)
- Profile switching = logout and login again
Profile Protection:
- Protected profiles require PIN on login
- PIN validated by Plex API during switch
- PIN not stored (only needed at login)
Benefits:
- Accurate request attribution ("Requested by Dad" vs "Requested by Kids")
- Personalized BookDate recommendations based on each profile's ratings
- Separate "My Requests" per family member
- Accurate logs and analytics
OIDC Authentication (Audiobookshelf Mode)
Status: ✅ Implemented | OpenID Connect support with comprehensive access control and admin role mapping
OIDC Provider Configuration
- Provider Name: Display name for login button (e.g., "Authentik", "Keycloak")
- Issuer URL: OIDC provider's issuer URL (must support
.well-known/openid-configuration) - Client ID/Secret: OAuth2 credentials from OIDC provider
- Required Scopes:
openid,profile,email,groups - Redirect URI:
{BASE_URL}/api/auth/oidc/callback
Access Control Methods
Controls who can log in to the application (separate from admin role assignment):
1. Open Access (open)
- Anyone who can authenticate with OIDC provider has access
- No additional restrictions
- Default: Suitable for trusted internal providers
2. Group/Claim Based (group_claim)
- Requires specific group/claim value for access
- Config:
oidc.access_group_claim(default:groups) - Config:
oidc.access_group_value(required group name) - Example: Only users in "readmeabook-users" group can log in
3. Allowed List (allowed_list)
- Whitelist of specific emails and/or usernames
- Config:
oidc.allowed_emails(JSON array) - Config:
oidc.allowed_usernames(JSON array) - Example:
["user1@example.com", "user2@example.com"]
4. Admin Approval (admin_approval)
- New users created in "pending_approval" state
- Admin must approve/reject users before they can access
- Pending users visible in admin settings
Admin Role Mapping
Automatically grants admin permissions based on OIDC claims (e.g., group membership):
Configuration:
oidc.admin_claim_enabled='true'|'false'(default:'false')oidc.admin_claim_name= claim field to check (default:'groups')oidc.admin_claim_value= required value for admin role (e.g.,'readmeabook-admin')
Behavior:
- First OIDC user always becomes admin (regardless of claim settings)
- Subsequent users checked against admin claim if enabled
- If claim matches → granted admin role
- If claim doesn't match → granted user role
- Claim check occurs on every login (role can be updated dynamically)
Example:
- Authentik group: Create
readmeabook-admingroup - Add users to group
- Configure:
oidc.admin_claim_value = 'readmeabook-admin' - Users in group get admin role on login
OIDC Endpoints
- GET /api/auth/oidc/login - Initiate OIDC flow, redirect to provider
- GET /api/auth/oidc/callback - Handle OAuth callback, create/update user, return JWT
- GET /api/auth/providers - List enabled auth providers for login page
- Returns:
backendMode,providers[],registrationEnabled,hasLocalUsers,oidcProviderName,localLoginDisabled,automationEnabled automationEnabled: true if Prowlarr/indexer configured (used for dynamic login page description)
- Returns:
Configuration Keys
oidc.enabled = 'true' | 'false'
oidc.provider_name = 'Authentik' (display name)
oidc.issuer_url = 'https://...'
oidc.client_id = 'xxx'
oidc.client_secret = (encrypted)
# Access Control
oidc.access_control_method = 'open' | 'group_claim' | 'allowed_list' | 'admin_approval'
oidc.access_group_claim = 'groups' (claim name)
oidc.access_group_value = 'readmeabook-users' (required group)
oidc.allowed_emails = '["user@example.com"]' (JSON array)
oidc.allowed_usernames = '["username"]' (JSON array)
# Admin Role Mapping
oidc.admin_claim_enabled = 'true' | 'false'
oidc.admin_claim_name = 'groups'
oidc.admin_claim_value = 'readmeabook-admin'
Implementation
- Provider:
src/lib/services/auth/OIDCAuthProvider.ts - Routes:
src/app/api/auth/oidc/login/route.ts,src/app/api/auth/oidc/callback/route.ts - Setup Wizard:
src/app/setup/steps/OIDCConfigStep.tsx - Admin Settings: OIDC section in
/admin/settings(auth tab) - Library:
openid-client(OIDC discovery, token exchange, PKCE)
Security
- Never log tokens
- HTTPS only in production
- Short access token expiry (1hr)
- Optional refresh token rotation
- Track valid tokens for revocation
- Server access verification: Uses stored
machineIdentifierfrom config (no API call needed)- Only users with access to the configured Plex server can authenticate
- Prevents any Plex user from accessing the instance
- machineIdentifier stored during setup/settings configuration (architectural optimization)
- OIDC PKCE: All OIDC flows use PKCE (Proof Key for Code Exchange) for enhanced security
Tech Stack
- Custom Plex OAuth (direct API)
- OIDC: openid-client (npm)
- jsonwebtoken (npm)
- Node.js crypto