Authentication
Overview
The Exam Portal uses Firebase Authentication for secure, scalable user management across all roles.
Authentication Methods
Email & Password
Standard email/password authentication
curl -X POST https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"password": "password123",
"returnSecureToken": true
}'
Anonymous Authentication
For guest exam access
firebase.auth().signInAnonymously()
.then(result => {
// Use result.user.uid for guest tracking
});
JWT Token Format
Firebase ID tokens are JWTs containing:
iss: Issuer (Firebase project)aud: Audience (Firebase project ID)auth_time: Authentication timeuser_id: Firebase UIDsub: Subject (same as user_id)iat: Issued atexp: Expiration (1 hour)
Token Refresh
Tokens automatically refresh when near expiry:
firebase.auth().onIdTokenChanged(user => {
if (user) {
user.getIdToken().then(token => {
// Use fresh token
});
}
});
Authorization
Role-Based Access Control (RBAC)
Super Admin
- All endpoints
- Full system access
- Tenant management
Client Admin
- Organization-specific endpoints
- Cannot access other organizations
- Can manage users within organization
Student
- Can view own profile
- Can take exams
- Can view own results
Guest
- Limited to exam access via share code
- Anonymous identification
Permission Matrix
| Endpoint | Super Admin | Client Admin | Student | Guest |
|---|---|---|---|---|
| GET /clients | ✅ | ❌ | ❌ | ❌ |
| POST /clients | ✅ | ❌ | ❌ | ❌ |
| GET /tests | ✅ | ✅* | ✅* | ❌ |
| POST /tests | ✅ | ✅* | ❌ | ❌ |
| POST /attempts | ✅ | ✅* | ✅ | ✅ |
| GET /attempts | ✅ | ✅* | ✅* | ✅* |
*Restricted to organization's own data
Security Best Practices
Token Handling
- Never store tokens in localStorage for sensitive operations
- Use httpOnly cookies when possible
- Always validate token expiry
- Refresh tokens proactively
BOLA/IDOR Prevention
// ✅ Correct: Verify user access
const userId = req.auth.uid;
const resource = await getResource(id, userId);
// ❌ Wrong: Direct access without verification
const resource = await getResource(id);
Cross-Tenant Isolation
// ✅ Correct: Always include client_id check
const tests = await db
.select('*')
.from('tests')
.where('id', testId)
.andWhere('client_id', userClientId);
// ❌ Wrong: Missing client_id validation
const tests = await db.select('*').from('tests').where('id', testId);
Audit Logging
All authentication events are logged:
{
"event_type": "login",
"user_id": "user-uuid",
"ip_address": "192.168.1.1",
"success": true,
"timestamp": "2024-01-15T10:00:00Z"
}
Troubleshooting
Token Expired
Error: ID token has expired
Solution: Refresh token with firebase.auth().currentUser.getIdToken(true)
Permission Denied
Error: 403 Forbidden
Solution: Check user role and permissions for the resource
Invalid Token
Error: 401 Unauthorized
Solution: Verify token is valid and not tampered with