42ff3e8f33
Build & Deploy / Test & Lint (push) Has been cancelled
Build & Deploy / Security Scan (push) Has been cancelled
Code Quality / Code Quality Checks (push) Has been cancelled
Code Quality / Dependency Vulnerabilities (push) Has been cancelled
Code Quality / Performance Testing (push) Has been cancelled
Build & Deploy / Build Docker Images (push) Has been cancelled
Build & Deploy / Deploy to Kubernetes (push) Has been cancelled
20 KiB
20 KiB
Mode1Test Token Flow
Overview
Mode1Test uses PocketBase as the OAuth2 provider for Microsoft authentication. The system manages two types of tokens:
- Graph Access Token (short-lived, 1 hour) - Stored in localStorage
- Refresh Token (long-lived, ~30 days) - Managed internally by PocketBase
Architecture Diagram
┌─────────────────────────────────────────────────────────────────┐
│ Mode1Test Token Architecture │
└─────────────────────────────────────────────────────────────────┘
┌──────────────────────┐
│ Microsoft OAuth2 │
│ (Microsoft Entra) │
└──────────┬───────────┘
│
┌────────────┴────────────┐
│ │
▼ ▼
Access Token (1hr) Refresh Token (30d)
├─ For Graph API └─ Stored in pb.authStore
│ (PocketBase internal)
│
▼
localStorage
'graphAccessToken'
│
├──────────────┬──────────────┐
│ │ │
▼ ▼ ▼
Frontend Backend Session
(index.html) (/api/...) (Memory)
Reads token Receives Persists
from store x-graph-token until logout
for API calls headers
1. Sign-In Flow Diagram
USER INITIATES LOGIN
│
▼
┌─────────────────────────────────┐
│ signin.html │
│ Click "Sign in with Microsoft" │
└────────┬────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ pb.authWithOAuth2({ │
│ provider: 'microsoft' │
│ }) │
└────────┬────────────────────────┘
│
▼
[Browser redirects to Microsoft OAuth]
│
▼
┌─────────────────────────────────┐
│ User grants permissions │
│ Microsoft validates credentials │
└────────┬────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Microsoft returns to signin.html │
│ With authorization code │
└────────┬────────────────────────────────┘
│
▼
┌──────────────────────────────────────────┐
│ PocketBase exchanges code for tokens: │
│ • Access Token (Graph API) │
│ • Refresh Token (for later use) │
│ • User data │
└────────┬─────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ displayUserInfo(authData) called │
│ • Extract Graph token from authData │
│ • Store in localStorage │
│ • Show authenticated UI │
└────────┬────────────────────────────────┘
│
▼
USER AUTHENTICATED ✓
Ready to access job files
2. Token Acquisition (Detailed)
Frontend (signin.html):
async function login() {
const authData = await pb.collection('users').authWithOAuth2({
provider: 'microsoft',
});
displayUserInfo(authData);
}
const extractGraphToken = (authData) => {
return (
authData?.meta?.graphAccessToken ||
authData?.meta?.graph_token ||
authData?.meta?.graphToken ||
authData?.meta?.accessToken ||
authData?.meta?.access_token ||
authData?.meta?.token ||
authData?.meta?.rawToken ||
authData?.meta?.authData?.access_token ||
''
);
};
function displayUserInfo(authData) {
const graphToken = extractGraphToken(authData);
if (graphToken) {
localStorage.setItem('graphAccessToken', graphToken); // ← STORED
}
}
Location: Mode1Test/frontend/signin.html lines 20-50
3. Token Storage Diagram
┌──────────────────────────────────────────────────────────────┐
│ Token Storage Locations │
└──────────────────────────────────────────────────────────────┘
BROWSER CLIENT
│
├─ localStorage
│ └─ 'graphAccessToken'
│ ├─ Value: "eyJ0eXAiOiJKV1QiLCJhbGc..."
│ ├─ Source: Extracted from OAuth response
│ ├─ Lifetime: ~1 hour
│ └─ Access: fetch() headers in any page
│
└─ pb.authStore (PocketBase Internal)
├─ Refresh Token (secure, not exposed)
├─ Auth Token (PocketBase session)
├─ User Model (profile data)
├─ Managed: By PocketBase SDK automatically
└─ Persists: Across page reloads
BACKEND
│
└─ Request Header: x-graph-token
├─ Source: Sent from frontend
├─ Usage: Passed to Microsoft Graph API
└─ Not Stored: Used immediately, discarded after request
4. API Call Flow (How Tokens Are Used)
USER ACTION (index.html)
│
▼
READ TOKEN FROM localStorage
│
├─ Token found?
│ ├─ YES → Continue
│ └─ NO → Redirect to signin.html
│
▼
MAKE API REQUEST
│
fetch('/api/job-files?link=...', {
headers: {
'x-graph-token': token ← Token sent here
}
})
│
▼
BACKEND RECEIVES (server.ts)
│
const token = c.req.header('x-graph-token')
│
▼
CALL MICROSOFT GRAPH API
│
fetch('https://graph.microsoft.com/v1.0/...', {
headers: {
Authorization: `Bearer ${token}`
}
})
│
├─ Success (200) ──┐
│ ▼
│ RETURN FILES TO FRONTEND
│ │
│ ▼
│ DISPLAY IN UI
│
└─ Token Expired (401)
│
▼
RETURN 401 TO FRONTEND
│
▼
FRONTEND DETECTS 401
│
▼
REDIRECT TO signin.html?reauth=1
│
▼
TRIGGER TOKEN REFRESH (see section 5)
5. Token Refresh Flow Diagram
TOKEN EXPIRED (401 from Graph API)
│
▼
┌──────────────────────────────────────────┐
│ Frontend receives 401 error │
│ (from /api/job-files endpoint) │
└────────┬─────────────────────────────────┘
│
▼
┌──────────────────────────────────────────┐
│ Set flag & redirect: │
│ localStorage.setItem( │
│ 'graphReauthAttempted', 'true' │
│ ) │
│ window.location.href = │
│ 'signin.html?reauth=1' │
└────────┬─────────────────────────────────┘
│
▼
┌──────────────────────────────────────────┐
│ signin.html (with reauth=1 param) │
│ │
│ Check if already authenticated: │
│ if (pb.authStore.isValid) { │
└────────┬─────────────────────────────────┘
│
▼
┌──────────────────────────────────────────┐
│ Call authRefresh(): │
│ await pb.collection('users') │
│ .authRefresh() │
│ │
│ PocketBase internally: │
│ • Uses stored refresh token │
│ • Exchanges with Microsoft │
│ • Gets new access token │
└────────┬─────────────────────────────────┘
│
▼
┌──────────────────────────────────────────┐
│ Extract new Graph token: │
│ const newGraphToken = │
│ extractGraphToken(authData) │
│ │
│ authData?.meta?.graphAccessToken ✓ │
└────────┬─────────────────────────────────┘
│
▼
┌──────────────────────────────────────────┐
│ Update localStorage: │
│ localStorage.setItem( │
│ 'graphAccessToken', newGraphToken │
│ ) │
│ localStorage.removeItem( │
│ 'graphReauthAttempted' │
│ ) │
└────────┬─────────────────────────────────┘
│
▼
┌──────────────────────────────────────────┐
│ Auto-redirect if reauth param set: │
│ const hasToken = │
│ localStorage.getItem( │
│ 'graphAccessToken' │
│ ) │
│ if (reauth && hasToken) { │
│ window.location.href = 'index.html' │
│ } │
└────────┬─────────────────────────────────┘
│
▼
BACK TO index.html
NEW TOKEN IN localStorage
RETRY API CALL ✓
6. Token Lifetime & Expiry
TIMELINE: Access Token Lifecycle
t=0min t=55min t=59min55s t=60min
│ │ │ │
├──────────────┼─────────────┼────────────┤
│ VALID │ VALID │ EXPIRED │
│ (Full) │ (5min │ (401 │
│ │ buffer) │ error) │
│ │ │ │
│ │ ← Frontend │ ← Redirect │
│ │ should │ to │
│ │ refresh │ signin │
│ │ soon │ │
KEY POINTS:
• Access Token: 1 hour (from Microsoft)
• Refresh Token: ~30 days (auto-managed by PocketBase)
• Monitor: No built-in expiry timer in Mode1Test
→ Errors surface when API call made with expired token
→ Frontend detects 401 and triggers refresh
7. Code Reference - Token Extraction
File: Mode1Test/frontend/signin.html (lines 20-50)
const extractGraphToken = (authData) => {
// Tries multiple possible locations where token might be
return (
authData?.meta?.graphAccessToken || // Primary location
authData?.meta?.graph_token || // Alternate spelling
authData?.meta?.graphToken || // Another variant
authData?.meta?.accessToken || // Generic name
authData?.meta?.access_token || // Underscore version
authData?.meta?.token || // Just "token"
authData?.meta?.rawToken || // Raw token
authData?.meta?.authData?.access_token || // Nested location
'' // Fallback: empty string
);
};
8. Code Reference - Token Usage
File: Mode1Test/backend/server.ts (lines 172-210)
// Get token from request header or environment
const getGraphToken = (c?: any) => {
const headerToken = c?.req?.header('x-graph-token') || ''; // From frontend
const envToken = process.env.GRAPH_TOKEN ||
process.env.MS_GRAPH_TOKEN || ''; // Fallback
const token = headerToken || envToken;
console.log('[getGraphToken] Header token:',
headerToken ? headerToken.substring(0, 20) + '...' : 'NONE');
console.log('[getGraphToken] Env token:', envToken ? 'SET' : 'NOT SET');
console.log('[getGraphToken] Using:',
token ? token.substring(0, 20) + '...' : 'NONE');
return token;
};
// Make API call to Microsoft Graph with token
const fetchJson = async (url: string, token: string) => {
const res = await fetch(url, {
headers: {
Authorization: `Bearer ${token}`, // Bearer scheme for Graph API
Accept: 'application/json',
},
});
if (!res.ok) {
// 401: Token expired or invalid
// 403: Insufficient permissions
// 404: Resource not found
const text = await res.text();
const err: any = new Error(`Graph ${res.status}: ${text}`);
err.status = res.status;
throw err;
}
return res.json();
};
// Example API endpoint that uses token
app.get('/api/job-files', async (c) => {
const token = getGraphToken(c); // Get token from header
if (!token) {
return c.json({ error: 'GRAPH_TOKEN missing' }, 500);
}
const link = c.req.query('link');
// ... uses fetchJson(url, token) to call Graph API
});
9. Storage Comparison
┌─────────────────────────────────────────────────────────────┐
│ localStorage vs pb.authStore │
├─────────────────────────────────────────────────────────────┤
│ │
│ localStorage (Visible) │
│ ├─ What: graphAccessToken (Graph API access) │
│ ├─ Where: Browser's persistent storage │
│ ├─ How: localStorage.getItem/setItem │
│ ├─ Clears: Never (until logout or script removes) │
│ ├─ Access: JavaScript can read/write │
│ └─ Risk: XSS attacks can steal token │
│ │
│ pb.authStore (Hidden) │
│ ├─ What: Refresh token + auth token + user data │
│ ├─ Where: PocketBase SDK internal storage │
│ ├─ How: pb.authStore.clear() / pb.authStore.model │
│ ├─ Clears: On logout() or pb.authStore.clear() │
│ ├─ Access: Limited to PocketBase SDK methods │
│ └─ Risk: Lower (not directly accessible) │
│ │
└─────────────────────────────────────────────────────────────┘
10. Error Handling Flowchart
API CALL ATTEMPT
│
▼
TOKEN AVAILABLE?
│
├─ NO → USER NOT AUTHENTICATED
│ └─ Redirect to signin.html ✓
│
└─ YES → SEND REQUEST WITH TOKEN
│
▼
RESPONSE STATUS?
│
├─ 200 OK → PROCESS DATA
│ └─ Display in UI ✓
│
├─ 401 UNAUTHORIZED
│ (Token expired/invalid)
│ └─ TRIGGER TOKEN REFRESH
│ └─ Redirect to signin.html?reauth=1
│ └─ PocketBase refreshes token
│ └─ Redirect back to index.html
│ └─ RETRY API CALL ✓
│
├─ 403 FORBIDDEN
│ (Insufficient permissions)
│ └─ SHOW ERROR MESSAGE
│ └─ User needs additional permissions
│
└─ 5XX SERVER ERROR
└─ RETRY LOGIC
└─ Exponential backoff (optional)
11. Testing the Token Flow
Verify Token in localStorage
// Browser console (any page after login)
localStorage.getItem('graphAccessToken')
// Output: "eyJ0eXAiOiJKV1QiLCJhbGc..."
Verify Token Refresh
// Browser console (signin.html with reauth=1)
const { pb } = window.Auth;
const authData = await pb.collection('users').authRefresh();
console.log('New token:', authData?.meta?.graphAccessToken?.substring(0, 30) + '...');
Test API Endpoint
# Get token from localStorage first, then:
curl -H "x-graph-token: eyJ0eXAi..." \
http://localhost:3000/api/job-files?link=https://czflex.sharepoint.com/...
12. Environment Configuration
File: secrets/.env (required)
# PocketBase connection (if running separate)
POCKETBASE_URL=https://pocketbase.ccllc.pro
# Microsoft OAuth (fallback Graph token if needed)
GRAPH_TOKEN=<your-microsoft-graph-token>
MS_GRAPH_TOKEN=<alternative-name>
# Server port
PORT=3000
Summary
Mode1Test Token Flow in 3 Steps:
-
Acquisition (signin.html):
- User clicks "Sign in with Microsoft"
- PocketBase handles OAuth2 handshake
- Extracts Graph token from response
- Stores in localStorage
-
Usage (index.html → server.ts):
- Frontend reads token from localStorage
- Sends in
x-graph-tokenheader - Backend receives and passes to Graph API
-
Refresh (When 401 occurs):
- Frontend redirects to signin.html?reauth=1
- PocketBase calls authRefresh()
- Uses internal refresh token (pb.authStore)
- Gets new Graph token from Microsoft
- Updates localStorage
- Auto-redirects back with fresh token
Key Files:
Mode1Test/frontend/signin.html- Token extraction & storageMode1Test/frontend/auth.js- Auth helper functionsMode1Test/backend/server.ts- Token usage in API endpoints