Files
2026-01-10 19:23:47 +00:00

692 lines
31 KiB
Markdown

================================================================================
AUTH FOLDER: COMPREHENSIVE ANALYSIS & TOKEN FLOW DOCUMENTATION
================================================================================
PROJECT: NewApproach
DATE: 2026-01-10
STATUS: In-depth analysis of auth module token acquisition, storage, and retrieval
================================================================================
FOLDER CONTENTS OVERVIEW
================================================================================
/NewApproach/auth/
├── auth.ts TypeScript implementation (source)
├── auth-universal.js JavaScript version (browser-compatible)
├── auth-test.html Interactive test interface for manual testing
└── README.md Documentation and quick-start guide
================================================================================
ARCHITECTURAL DESIGN
================================================================================
PATTERN-BASED CONFIGURATION:
- Auth system uses a "pattern" approach to enable only needed token types
- Patterns: 'pb', 'graph', 'pb+graph' (composable, can add more)
- This allows flexibility across different projects using different providers
SUPPORTED TOKEN TYPES (4 total):
1. 'pb-user' → PocketBase user authentication token
2. 'pb-agent' → PocketBase service account token (for server-to-server)
3. 'graph-user' → Microsoft Graph delegated token (on behalf of signed-in user)
4. 'graph-agent' → Microsoft Graph app-only token (service principal auth)
STORAGE MECHANISM:
- All tokens stored in browser localStorage (client-side storage)
- Storage keys use consistent naming pattern: 'auth:<token-type>'
- Examples:
* 'auth:pb-user' → stores PocketBase user token
* 'auth:graph-user' → stores Microsoft Graph delegated token
================================================================================
TOKEN ACQUISITION FLOW (WHERE & WHEN)
================================================================================
CRITICAL DISTINCTION: This module does NOT acquire tokens itself.
It is a STORAGE & RETRIEVAL system. Actual token acquisition happens elsewhere.
WHO ACQUIRES TOKENS?
- PocketBase tokens: PocketBase SDK (pb.authStore.token after user logs in)
- Graph tokens: Microsoft authentication library (via OAuth flow or app auth)
- These are obtained OUTSIDE this auth module
- This module then STORES and RETRIEVES them
THE FLOW:
1. EXTERNAL CODE ACQUIRES TOKEN
└─ Where: In user's application code or backend
└─ How: Via PocketBase SDK login or Microsoft authentication libraries
└─ When: After successful user authentication or service account setup
2. TOKEN IS STORED VIA Auth.setToken()
└─ Code: Auth.setToken('pb-user', <token-from-pocketbase>)
└─ Storage: localStorage under key 'auth:pb-user'
└─ When: Immediately after acquisition (outside this module)
3. TOKEN IS RETRIEVED VIA Auth.getToken()
└─ Code: const token = Auth.getToken('pb-user')
└─ Returns: String token or null if not found
└─ When: Whenever application needs to use the token (API calls, etc.)
================================================================================
STORAGE DETAILS (EXACT LOCATIONS & KEYS)
================================================================================
localStorage MAP:
Token Type │ Storage Key │ Where It Comes From
────────────────────┼────────────────────┼────────────────────────────────
'pb-user' │ 'auth:pb-user' │ PocketBase SDK after login
'pb-agent' │ 'auth:pb-agent' │ PocketBase SDK service account
'graph-user' │ 'auth:graph-user' │ MSAL or Graph auth library
'graph-agent' │ 'auth:graph-agent' │ Azure app authentication
STORAGE FORMAT:
- Raw string value (plaintext)
- Example: localStorage['auth:pb-user'] = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
- No serialization, just direct token string storage
PERSISTENCE:
- localStorage persists until:
* User manually clears browser data
* JavaScript calls localStorage.removeItem(key)
* Session expires (no automatic expiry in this module)
- Survives page refreshes and browser restarts (within same origin)
================================================================================
RETRIEVAL METHODS (HOW TO GET TOKENS BACK)
================================================================================
METHOD 1: Direct Retrieval
─────────────────────────
Auth.getToken('pb-user')
Returns: string | null
- Returns the token string if found
- Returns null if token was never set or was cleared
Usage Example:
const pbToken = Auth.getToken('pb-user');
if (pbToken) {
// Use token in API call
fetch('/api/users', {
headers: { 'Authorization': `Bearer ${pbToken}` }
});
}
Method Signature (TypeScript):
getToken(type: TokenType): string | null
Internal Implementation:
1. Accepts token type: 'pb-user', 'pb-agent', 'graph-user', or 'graph-agent'
2. Looks up storage key from TOKEN_STORAGE_KEYS map
3. Calls localStorage.getItem(key)
4. Returns result (string or null)
────────────────────────────────────────────────────────────────────────────
METHOD 2: Set Token (Store for Later Retrieval)
───────────────────────────────────────────────
Auth.setToken('pb-user', 'token-string-here')
Usage: When you receive a token from external auth system
// After PocketBase login:
const pbAuth = await pb.collection('users').authWithPassword(email, pass);
Auth.setToken('pb-user', pbAuth.token); // Now stored for later use
Method Signature (TypeScript):
setToken(type: TokenType, token: string | null): void
Internal Implementation:
1. Accepts token type and token string
2. Looks up storage key
3. If token is provided: localStorage.setItem(key, token)
4. If token is null: localStorage.removeItem(key)
────────────────────────────────────────────────────────────────────────────
METHOD 3: Clear Individual Token
────────────────────────────────
Auth.clearToken('pb-user')
Removes one token from storage.
Method Signature (TypeScript):
clearToken(type: TokenType): void
Internal Implementation:
Calls setToken(type, null) which removes the item
Usage: On logout or when token expires
Auth.clearToken('pb-user'); // PocketBase token removed
────────────────────────────────────────────────────────────────────────────
METHOD 4: Clear All Tokens
──────────────────────────
Auth.clearAllTokens()
Removes all stored tokens (all 4 types) in one call.
Method Signature (TypeScript):
clearAllTokens(): void
Internal Implementation:
Iterates through all TOKEN_STORAGE_KEYS
Calls localStorage.removeItem() for each key
Usage: On complete logout
Auth.clearAllTokens(); // All tokens removed
────────────────────────────────────────────────────────────────────────────
METHOD 5: Configuration & Setup
────────────────────────────────
await Auth.configure('pb+graph', { pbUrl: 'http://localhost:8090' })
Initializes the auth module with:
- Which patterns to enable ('pb', 'graph', 'pb+graph')
- Optional config (PocketBase URL, Graph API URL)
Method Signature (TypeScript):
async configure(patternString: string, config?: AuthConfig): Promise<void>
Internal Implementation:
1. Parses pattern string into array of token types
2. Stores pattern for validation
3. If pattern includes 'pb' tokens: initializes PocketBase SDK
4. If PocketBase library not loaded: throws error
CRITICAL: PocketBase must be loaded via <script> BEFORE calling configure()
Config Object Properties:
interface AuthConfig {
pbUrl?: string; // Default: http://127.0.0.1:8090
graphApiUrl?: string; // URL for Microsoft Graph API
}
────────────────────────────────────────────────────────────────────────────
METHOD 6: Query Module State
────────────────────────────
Auth.isConfigured(): boolean
Auth.getEnabledTypes(): TokenType[]
Check if module is ready and what token types are enabled.
Usage:
if (Auth.isConfigured()) {
const types = Auth.getEnabledTypes(); // Returns ['pb-user', 'pb-agent']
const token = Auth.getToken('pb-user');
}
================================================================================
FILE-BY-FILE BREAKDOWN
================================================================================
FILE 1: auth.ts (TypeScript Source)
═════════════════════════════════════
PURPOSE:
- Main implementation of AuthManager class
- Fully typed with TypeScript (strict mode)
- Source file (not browser-compatible directly)
TOKEN ACQUISITION POINT:
- NONE. This file does NOT acquire tokens.
- It only stores and retrieves tokens from localStorage
- External code must provide tokens via Auth.setToken()
TOKEN STORAGE MECHANISM:
- localStorage is the sole storage mechanism
- Keys defined in TOKEN_STORAGE_KEYS constant:
const TOKEN_STORAGE_KEYS: TokenConfig = {
'pb-user': 'auth:pb-user',
'pb-agent': 'auth:pb-agent',
'graph-user': 'auth:graph-user',
'graph-agent': 'auth:graph-agent'
};
CLASS STRUCTURE:
- AuthManager class (private instance state)
- Singleton exported as: export default Auth
- Also attached to window for browser use: window.Auth = Auth
KEY STATE VARIABLES:
- private pattern: TokenType[] = [] (which token types are enabled)
- private pbInstance: any = null (PocketBase SDK instance)
- private config: AuthConfig = {} (configuration passed by user)
METHOD IMPLEMENTATIONS:
1. configure(patternString, config?)
├─ Parses pattern string: 'pb+graph' → ['pb-user', 'pb-agent', 'graph-user', 'graph-agent']
├─ Actually only returns ['pb', 'graph'] because parsePattern treats 'pb' as a prefix filter
├─ Stores config
└─ If pattern includes 'pb': calls initPocketBase()
2. parsePattern(pattern: string): TokenType[]
├─ Splits by '+'
├─ Trims whitespace
├─ Filters empty strings
├─ Casts to TokenType[]
└─ QUIRK: This is overly simple and may not work correctly for all cases
3. initPocketBase()
├─ Gets PocketBase from window.PocketBase
├─ Creates instance with URL from config
├─ Throws if window.PocketBase not loaded
└─ Stores in this.pbInstance (but never used anywhere)
4. getToken(type: TokenType): string | null
├─ Looks up TOKEN_STORAGE_KEYS[type]
├─ Calls localStorage.getItem(key)
└─ Returns result (string | null)
5. setToken(type: TokenType, token: string | null): void
├─ Looks up TOKEN_STORAGE_KEYS[type]
├─ If token is truthy: localStorage.setItem(key, token)
└─ If token is falsy: localStorage.removeItem(key)
6. clearToken(type: TokenType): void
└─ Calls setToken(type, null)
7. clearAllTokens(): void
└─ Iterates all TOKEN_STORAGE_KEYS and removes each
DEPENDENCIES:
- localStorage (browser API) - REQUIRED
- PocketBase (window.PocketBase) - Optional, only if using 'pb' pattern
- No npm dependencies
SECURITY NOTES:
- Tokens stored in plaintext localStorage
- Accessible to any JavaScript on the page
- Vulnerable to XSS attacks
- Not suitable for highly sensitive credentials
- Production: consider HTTP-only cookies on backend
────────────────────────────────────────────────────────────────────────────
FILE 2: auth-universal.js (JavaScript Version)
════════════════════════════════════════════════
PURPOSE:
- Browser-compatible JavaScript version of auth.ts
- Simpler implementation, less feature-rich
- Can be used without TypeScript compilation
STRUCTURE:
- IIFE (Immediately Invoked Function Expression)
- Exposes Auth object to window.Auth
- Returns object with public methods
TOKEN ACQUISITION POINT:
- NONE. Same as auth.ts.
- Use Auth.setToken() to store tokens from external sources
TOKEN STORAGE:
- localStorage with keys defined in TOKEN_KEYS:
const TOKEN_KEYS = {
'pb-user': 'pbUserToken',
'pb-agent': 'pbAgentToken',
'graph-user': 'graphUserToken',
'graph-agent': 'graphAgentToken'
};
IMPORTANT DIFFERENCE FROM auth.ts:
- DIFFERENT STORAGE KEY NAMES!
- auth.ts uses: 'auth:pb-user'
- auth-universal.js uses: 'pbUserToken'
- ⚠️ INCOMPATIBLE: Tokens stored by one won't be readable by the other!
STATE VARIABLES:
let pattern = 'pb-user' (default pattern)
let enabledTypes = ['pb-user'] (which types are active)
let pb = null (PocketBase instance)
let popup = null (for OAuth popups, not implemented)
let agentCreds = { email: '', password: '' } (credentials storage, not used)
let superuserCreds = { email: '', password: '' } (credentials storage, not used)
PUBLIC METHODS:
window.Auth.use(type) │ Set active pattern
window.Auth.setPB(pbInstance) │ Inject PocketBase instance
window.Auth.getToken(type) │ Retrieve token from localStorage
window.Auth.setToken(type, token) │ Store token to localStorage
METHOD IMPLEMENTATIONS:
1. use(type)
├─ Sets pattern variable
├─ Parses type string into enabledTypes array
├─ If pattern includes 'pb': calls initPocketBase()
└─ Returns 'this' for chaining
2. setPB(pbInstance)
├─ Allows external PocketBase instance injection
└─ Stores in pb variable
3. initPocketBase()
├─ Returns early if pb already set
├─ Creates new PocketBase('http://127.0.0.1:8090')
└─ Stores in pb variable
4. getToken(type)
├─ Looks up TOKEN_KEYS[type]
├─ Returns localStorage.getItem(key) or null
└─ Same behavior as auth.ts version
5. setToken(type, token)
├─ Looks up TOKEN_KEYS[type]
├─ If token: localStorage.setItem(key, token)
└─ If !token: localStorage.removeItem(key)
UNUSED/INCOMPLETE:
- popup variable (defined but never used)
- agentCreds, superuserCreds (defined but never used)
- Suggests incomplete refactoring or planned features
DEPENDENCIES:
- localStorage (browser API)
- PocketBase (optional, can be injected via setPB())
────────────────────────────────────────────────────────────────────────────
FILE 3: auth-test.html (Testing & Validation)
═══════════════════════════════════════════════
PURPOSE:
- Interactive HTML test page
- Manual verification of auth module functionality
- No automated testing, all tests are button-triggered
TOKEN ACQUISITION:
- NONE. This file does NOT acquire tokens.
- Tests localStorage directly to verify storage mechanics
HOW IT WORKS:
- Loads auth-universal.js (or auth.ts compiled)
- Provides buttons that trigger JavaScript functions
- Functions test module availability and storage
TEST SECTIONS:
1. Module Availability Test
├─ Button: "Test Module Load"
├─ Tests: typeof window.Auth !== 'undefined'
└─ Verifies: Auth module is accessible globally
2. Token Storage Test
├─ Button: "Test Token Storage"
├─ Tests:
│ ├─ localStorage.setItem('test:token', 'test-value')
│ ├─ localStorage.getItem('test:token')
│ └─ localStorage.removeItem('test:token')
└─ Verifies: localStorage works correctly
3. Configuration Test
├─ Button: "Test Configuration"
├─ Tests: typeof window.Auth.configure === 'function'
└─ Verifies: Auth.configure method exists
4. Clear Tokens Test
├─ Button: "Clear All Tokens"
├─ Steps:
│ ├─ Creates test tokens: 'auth:pb-user', 'auth:graph-user'
│ ├─ Removes them via localStorage.removeItem()
│ └─ Verifies tokens are gone
└─ Tests: Auth module clearing capability
LIMITATIONS:
- Tests use direct localStorage, not Auth.clearAllTokens()
- Tests auth-test.html keys, not actual auth module keys
- Not comprehensive
- No error handling for edge cases
- No async test support
────────────────────────────────────────────────────────────────────────────
FILE 4: README.md (Documentation)
══════════════════════════════════
PURPOSE:
- Public-facing documentation
- Quick-start guide for developers
KEY SECTIONS:
1. Overview: Describes pattern-based approach and features
2. Quick Start: Code examples showing basic usage
3. Token Types: Lists all 4 token types
4. Configuration: Documents AuthConfig interface
5. Security Considerations: Plaintext localStorage warning
6. File Structure: Simple directory listing
7. Testing: Refers to auth-test.html
8. Future Enhancements: Planned features (refresh logic, expiration tracking, etc.)
CRITICAL SECURITY WARNING FROM DOCS:
- "Tokens are stored in plaintext in localStorage"
- "Never store highly sensitive credentials in browser localStorage"
- "For production: consider using secure HTTP-only cookies via backend"
================================================================================
COMPLETE TOKEN FLOW DIAGRAM
================================================================================
┌─────────────────────────────────────────────────────────────────────────┐
│ EXTERNAL AUTH SOURCE │
│ (PocketBase SDK, MSAL, Azure App Auth, etc.) │
└────────────────────────┬────────────────────────────────────────────────┘
│ TOKEN ACQUIRED
│ (user login, service auth, OAuth)
┌──────────────────────────────────┐
│ Auth.setToken('type', token) │ ← CODE STORES TOKEN
└────────────┬─────────────────────┘
│ Calls: localStorage.setItem(key, token)
│ Key Example: 'auth:pb-user'
┌──────────────────────────────────┐
│ Browser localStorage │ ← PERSISTENT STORAGE
│ 'auth:pb-user' → 'eyJhbG...' │
│ 'auth:graph-user' → 'ya29.a...' │
└──────────────────────────────────┘
│ On page load or API call needed
┌────────────▼──────────────────────┐
│ Auth.getToken('pb-user') │ ← CODE RETRIEVES TOKEN
└────────────┬─────────────────────┘
│ Calls: localStorage.getItem('auth:pb-user')
│ Returns: 'eyJhbG...' or null
┌────────────▼──────────────────────┐
│ Application Uses Token │
│ (API calls, authenticated │
│ requests, etc.) │
└──────────────────────────────────┘
================================================================================
IMPLEMENTATION GOTCHAS & KNOWN ISSUES
================================================================================
GOTCHA 1: STORAGE KEY MISMATCH
Issue: auth.ts and auth-universal.js use DIFFERENT storage keys
- auth.ts: 'auth:pb-user'
- auth-universal.js: 'pbUserToken'
Impact: If you use both files, tokens won't be visible across implementations
Fix: Standardize on one set of keys
────────────────────────────────────────────────────────────────────────────
GOTCHA 2: POCKETBASE NEVER USED
Issue: initPocketBase() creates instance but it's never actually used
- Stored in this.pbInstance but no methods call it
- No token refresh logic
- No authentication flows
Impact: PocketBase initialization is pointless
Fix: Either remove it or implement actual PocketBase auth flows
────────────────────────────────────────────────────────────────────────────
GOTCHA 3: PLAINTEXT TOKEN STORAGE
Issue: Tokens stored in plain text in localStorage
- Accessible to any JavaScript on the page
- Vulnerable to XSS attacks
- Session-persistent (no expiry tracking)
Impact: Not suitable for highly sensitive credentials
Fix: For production, implement HTTP-only cookies via backend
────────────────────────────────────────────────────────────────────────────
GOTCHA 4: UNUSED STATE VARIABLES (auth-universal.js)
Issue: Variables defined but never used:
- popup (for OAuth, not implemented)
- agentCreds, superuserCreds (storage, not implemented)
Impact: Code is confusing, suggests incomplete refactoring
Fix: Remove unused variables or implement their intended functionality
────────────────────────────────────────────────────────────────────────────
GOTCHA 5: NO PATTERN VALIDATION
Issue: parsePattern() doesn't validate that parsed strings are actual TokenTypes
- 'pb' gets parsed as 'pb' but valid types are ['pb-user', 'pb-agent']
- Calling Auth.getToken('pb') will fail (not a valid TokenType)
Impact: Configuration can silently set invalid patterns
Fix: Validate pattern strings against valid TokenType list
────────────────────────────────────────────────────────────────────────────
GOTCHA 6: NO TOKEN EXPIRY TRACKING
Issue: Module stores tokens but doesn't track expiration
- No way to know if token is expired
- No automatic refresh logic
Impact: Stale/expired tokens can be used in API calls
Fix: Implement expiration tracking and refresh logic (noted as future enhancement)
────────────────────────────────────────────────────────────────────────────
GOTCHA 7: NO ERROR BOUNDARIES
Issue: If localStorage is disabled/unavailable, getToken/setToken fail silently
- Try/catch not implemented
- No error logging
Impact: Hard to debug issues with storage failures
Fix: Add error handling and logging
================================================================================
USAGE PATTERNS: WHEN TOKENS ARE ACQUIRED & USED
================================================================================
PATTERN 1: USER CREDENTIALS LOGIN (PocketBase)
───────────────────────────────────────────────
Timeline:
1. User enters email/password
2. Frontend code calls: pb.collection('users').authWithPassword(email, pwd)
3. PocketBase returns: { token: 'eyJhbG...', user: {...} }
4. Frontend calls: Auth.setToken('pb-user', response.token)
5. Token stored in: localStorage['auth:pb-user'] = 'eyJhbG...'
6. Later, to use token: const token = Auth.getToken('pb-user')
7. In API headers: { 'Authorization': `Bearer ${token}` }
WHEN IS TOKEN ACQUIRED: During user login
WHERE IS TOKEN ACQUIRED: From PocketBase SDK response
WHERE IS IT STORED: localStorage under key 'auth:pb-user'
WHERE IS IT RETRIEVED: Via Auth.getToken('pb-user') when needed
────────────────────────────────────────────────────────────────────────────
PATTERN 2: SERVICE ACCOUNT AUTHENTICATION (PocketBase)
────────────────────────────────────────────────────────
Timeline:
1. Backend has service account credentials (email/password)
2. Backend authenticates: pb.collection('_superusers').authWithPassword(...)
3. Backend receives token and stores in Auth.setToken('pb-agent', token)
4. Frontend retrieves: Auth.getToken('pb-agent')
5. Used for server-to-server operations
WHEN IS TOKEN ACQUIRED: During backend service startup
WHERE IS TOKEN ACQUIRED: From PocketBase SDK (backend context)
WHERE IS IT STORED: localStorage['auth:pb-agent']
WHERE IS IT RETRIEVED: Via Auth.getToken('pb-agent')
────────────────────────────────────────────────────────────────────────────
PATTERN 3: MICROSOFT GRAPH DELEGATED TOKEN
─────────────────────────────────────────────
Timeline:
1. User initiates OAuth sign-in via Microsoft
2. MSAL library handles OAuth flow
3. MSAL returns: { accessToken: 'eyJ0eXAi...', expiresIn: 3600, ... }
4. Code stores: Auth.setToken('graph-user', response.accessToken)
5. Token stored in: localStorage['auth:graph-user']
6. Used in Graph API calls: fetch('https://graph.microsoft.com/v1.0/me', ...)
WHEN IS TOKEN ACQUIRED: During OAuth flow completion
WHERE IS TOKEN ACQUIRED: From MSAL authentication library
WHERE IS IT STORED: localStorage['auth:graph-user']
WHERE IS IT RETRIEVED: Via Auth.getToken('graph-user')
────────────────────────────────────────────────────────────────────────────
PATTERN 4: MICROSOFT GRAPH APP-ONLY TOKEN (Service Principal)
──────────────────────────────────────────────────────────────
Timeline:
1. Backend authenticates as service principal (not user)
2. Uses client credentials flow (clientId + clientSecret)
3. Azure AD returns: { access_token: 'eyJ0eXAi...', expires_in: 3600 }
4. Code stores: Auth.setToken('graph-agent', response.access_token)
5. Token stored in: localStorage['auth:graph-agent']
6. Used for app-to-app operations
WHEN IS TOKEN ACQUIRED: During service principal auth
WHERE IS TOKEN ACQUIRED: From Azure AD token endpoint
WHERE IS IT STORED: localStorage['auth:graph-agent']
WHERE IS IT RETRIEVED: Via Auth.getToken('graph-agent')
================================================================================
SUMMARY: QUICK REFERENCE
================================================================================
TOKENS ACQUIRED BY: External authentication systems (not this module)
TOKENS STORED BY: Auth.setToken() → localStorage
TOKENS RETRIEVED BY: Auth.getToken() → returns string or null
STORAGE LOCATION: Browser localStorage
STORAGE KEYS:
- 'auth:pb-user' (auth.ts version)
- 'auth:pb-agent' (auth.ts version)
- 'auth:graph-user' (auth.ts version)
- 'auth:graph-agent' (auth.ts version)
- 'pbUserToken' (auth-universal.js version)
- 'pbAgentToken' (auth-universal.js version)
- 'graphUserToken' (auth-universal.js version)
- 'graphAgentToken' (auth-universal.js version)
PUBLIC API:
await Auth.configure(pattern, config?) Setup module
Auth.getToken(type: TokenType) Retrieve token (returns string|null)
Auth.setToken(type: TokenType, token) Store token
Auth.clearToken(type: TokenType) Remove one token
Auth.clearAllTokens() Remove all tokens
Auth.isConfigured() Check if ready
Auth.getEnabledTypes() Get active token types
SECURITY CONCERN: Plaintext localStorage
- Not suitable for highly sensitive credentials
- Vulnerable to XSS
- Production: use HTTP-only cookies on backend
MAIN IMPLEMENTATION FILE: auth.ts (TypeScript)
BROWSER VERSION: auth-universal.js (JavaScript)
TEST PAGE: auth-test.html (manual testing)
================================================================================
END OF AUTH FOLDER ANALYSIS
================================================================================