1fdc87f7f4
- Created Mode1 as self-contained test environment - Added auth-module copy with self-contained configuration - Implemented backend server with Graph token and PocketBase validation endpoints - Added frontend test dashboard with token comparison functionality - Fixed TypeScript compatibility issues (replaced import.meta.dir with __dirname) - Implemented actual PB token validation in comparison endpoint (no placeholders) - All endpoints tested and working with real tokens
Auth Module
Standalone authentication module for PocketBase OAuth2 + Microsoft Graph integration.
Features
- Frontend: PocketBase OAuth2 authentication with Microsoft provider
- Backend: Microsoft Graph token management with automatic caching
- No dependencies on project-specific code - Use in any project
Installation
Copy the auth-module folder to your project:
cp -r auth-module /path/to/your/project/
Install dependencies:
bun add pocketbase @azure/msal-node
Frontend Usage
Basic Setup
Important: The frontend is browser-side code. It must receive pbUrl from the consuming application (not from the backend).
import { PocketBaseAuth } from './auth-module/frontend';
// In your project, load PB_URL from environment/config and pass it to frontend
const auth = new PocketBaseAuth({
pbUrl: process.env.PB_URL, // Required - must be provided by consuming application
collection: 'Users',
provider: 'microsoft',
loginContainerId: 'loginContainer',
userDisplayNameId: 'userDisplayName',
userEmailId: 'userEmailValue',
loginBtnId: 'loginBtn',
loginErrorId: 'loginError',
});
HTML Required
<!-- Login container (shown when not authenticated) -->
<div id="loginContainer" class="hidden">
<button id="loginBtn">Login with Microsoft</button>
<div id="loginError" class="hidden"></div>
</div>
<!-- User display (shown when authenticated) -->
<div id="userDisplayName"></div>
<div id="userEmailValue"></div>
Event Callbacks
const auth = new PocketBaseAuth(config, {
onAuthSuccess: (state) => {
console.log('Logged in:', state.user.email);
// Initialize other systems here
},
onAuthFailure: (error) => {
console.error('Login failed:', error);
},
onTokenUpdate: (state) => {
console.log('Token refreshed');
},
onUiUpdate: (state) => {
console.log('UI updated');
},
});
Methods
// Get current auth state
const state = auth.getAuthState();
console.log(state.isAuthenticated, state.user);
// Check token status
const status = await auth.checkTokenStatus();
// Logout
auth.logout();
// Get raw PocketBase instance if needed
const pb = auth.getPocketBase();
Backend Usage
Setup (server.ts)
import { GraphTokenManager, PocketBaseValidator, BackendAuth } from './auth-module/backend';
// Option 1: Use individual managers
const graphMgr = new GraphTokenManager({
clientId: process.env.CLIENT_ID,
tenantId: process.env.TENANT_ID,
clientSecret: process.env.CLIENT_SECRET,
});
const pbValidator = new PocketBaseValidator(process.env.PB_DB);
// Option 2: Use combined manager
const backendAuth = new BackendAuth({
clientId: process.env.CLIENT_ID,
tenantId: process.env.TENANT_ID,
clientSecret: process.env.CLIENT_SECRET,
}, process.env.PB_DB);
Get Graph Token
// Get token string
const token = await graphMgr.getToken();
// Get token with expiration
const { token, expiresOnISO } = await graphMgr.getTokenWithExpiry();
// Check if cached and valid
if (graphMgr.isTokenValid()) {
console.log('Using cached token');
}
// Clear cache (force refresh)
graphMgr.clearCache();
Validate PocketBase Token
// Validate token
const isValid = await pbValidator.validateUserToken(token);
// Get user record
const user = await pbValidator.getUserRecord(token);
// Get PocketBase instance
const pb = pbValidator.getPocketBase();
Endpoint Example
app.get('/api/graph/status', async (c) => {
try {
const { token, expiresOnISO } = await graphMgr.getTokenWithExpiry();
return c.json({
active: true,
expiresOnISO,
});
} catch (error) {
return c.json(
{ active: false, error: error.message },
500
);
}
});
app.post('/api/submit', async (c) => {
const body = await c.req.json();
const pbToken = body.pbToken;
if (!pbToken) {
return c.json({ error: 'Missing pbToken' }, 401);
}
const isValid = await pbValidator.validateUserToken(pbToken);
if (!isValid) {
return c.json({ error: 'Invalid token' }, 401);
}
// Token is valid, proceed with business logic
// ...
});
Environment Variables
Required in .env file:
CLIENT_ID=your-microsoft-app-id
TENANT_ID=your-azure-tenant-id
CLIENT_SECRET=your-microsoft-client-secret
PB_DB=https://your-pocketbase-instance.com
PB_URL=https://your-pocketbase-instance.com
Configuration
Frontend Config Options
interface AuthConfig {
pbUrl: string; // PocketBase URL (required)
collection?: string; // Auth collection (default: Users)
provider?: string; // OAuth provider (default: microsoft)
loginContainerId?: string; // ID of login container element
userDisplayNameId?: string; // ID of user name display element
userEmailId?: string; // ID of user email display element
loginBtnId?: string; // ID of login button element
loginErrorId?: string; // ID of error message element
}
Backend Config Options
interface BackendAuthConfig {
clientId?: string; // Microsoft app ID (or CLIENT_ID env var)
tenantId?: string; // Azure tenant ID (or TENANT_ID env var)
clientSecret?: string; // Client secret (or CLIENT_SECRET env var)
}
TypeScript
The module exports TypeScript types for type safety:
import { AuthState, AuthConfig, GraphTokenCache } from './auth-module/types';
const state: AuthState = auth.getAuthState();
Notes
- Frontend manages user authentication only
- Backend manages Graph API tokens (never exposed to client)
- Tokens are cached in-memory on backend with automatic refresh
- Module does not include project-specific features (alerts, etc.)
- Each project can implement its own business logic on top
License
Same as parent project