Authentication System
PBS Knowledge uses a dual authentication system combining SAML 2.0 for Dartmouth SSO integration and Firebase Authentication for session management and token handling.
Architecture Overview
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Browser │────▶│ Dartmouth │────▶│ Backend │
│ (User) │◀────│ IdP (CAS) │◀────│ (Fastify) │
└──────────────┘ └──────────────┘ └──────────────┘
│ │
│ ▼
│ ┌──────────────┐
└─────────────────────────────────▶│ Firebase │
│ Auth │
└──────────────┘
Flow:
- User clicks "Sign In with Dartmouth"
- Backend redirects to Dartmouth SAML IdP
- User authenticates with Dartmouth credentials
- IdP posts SAML assertion back to backend
- Backend validates assertion, creates/updates Firebase user
- Backend generates Firebase custom token
- Frontend exchanges token for Firebase session
SAML 2.0 Integration
Dartmouth Identity Provider
PBS Knowledge integrates with Dartmouth's Central Authentication Service (CAS) via SAML 2.0:
- IdP Entity ID:
https://login.dartmouth.edu/idp/shibboleth - SSO URL:
https://login.dartmouth.edu/cas/idp/profile/SAML2/POST/SSO - Logout URL:
https://logout.dartmouth.edu
Service Provider Configuration
The application acts as a SAML Service Provider (SP):
| Setting | Value |
|---|---|
| Entity ID | https://dartmouthpbs.org |
| ACS URL | https://dartmouthpbs.org/api/auth/saml/callback |
| NameID Format | urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified |
| Signing | SHA-256 |
Environment Variables
# Service Provider Configuration
SAML_ENTITY_ID=https://dartmouthpbs.org
SAML_CALLBACK_URL=https://dartmouthpbs.org/api/auth/saml/callback
# Identity Provider Configuration
SAML_IDP_SSO_URL=https://login.dartmouth.edu/cas/idp/profile/SAML2/POST/SSO
SAML_IDP_LOGOUT_URL=https://logout.dartmouth.edu
SAML_IDP_CERT=<Dartmouth IdP Certificate>
# SP Signing Keys (for signed AuthnRequests)
SAML_KEY_PATH=/path/to/private-key.pem
SAML_CERT_PATH=/path/to/certificate.pem
SAML Attributes
The following attributes are requested from Dartmouth's IdP:
| Attribute | OID | Description |
|---|---|---|
eduPersonPrincipalName | 1.3.6.1.4.1.5923.1.1.1.6 | Primary identifier (NetID@dartmouth.edu) |
mail | 0.9.2342.19200300.100.1.3 | Email address |
givenName | 2.5.4.42 | First name |
sn | 2.5.4.4 | Last name (surname) |
displayName | 2.16.840.1.113730.3.1.241 | Display name |
eduPersonAffiliation | 1.3.6.1.4.1.5923.1.1.1.1 | Affiliation (faculty, staff, student) |
ou | 2.5.4.11 | Organizational unit (department) |
User Extraction
The SAMLAuthService extracts user information from the SAML assertion:
interface SAMLUser {
netid: string; // Dartmouth NetID (from ePPN)
email: string; // Email address
firstName?: string; // Given name
lastName?: string; // Surname
displayName?: string; // Full display name
affiliation?: string[]; // Roles (faculty, staff, student)
department?: string; // Department/OU
}
Role Mapping
User roles are determined from the eduPersonAffiliation attribute:
| Affiliation | Assigned Role |
|---|---|
| faculty, employee | faculty |
| staff | staff |
| student | student |
| alum | user |
| (none) | user |
Note: Admin role is never auto-assigned from SAML. Existing admin roles are preserved during re-authentication.
API Endpoints
SAML Routes
GET /api/auth/saml/login
Initiates SAML authentication by redirecting to Dartmouth IdP.
Query Parameters:
returnTo(optional): URL path to redirect after authentication
# Redirect to Dartmouth SSO
GET /api/auth/saml/login?returnTo=/dashboard
POST /api/auth/saml/callback
Receives and validates SAML assertion from IdP. This is the Assertion Consumer Service (ACS) URL.
Flow:
- Validates SAML assertion signature (when IdP cert configured)
- Extracts user attributes
- Creates or updates Firebase user
- Generates Firebase custom token
- Redirects to frontend with token
GET /api/auth/saml/logout
Initiates SAML logout by redirecting to Dartmouth logout URL.
GET /api/auth/saml/metadata
Returns SP metadata XML for registration with Dartmouth IT.
Firebase Routes
POST /api/auth/login
Verifies Firebase ID token and returns user info.
// Request
{ "idToken": "firebase-id-token" }
// Response
{
"user": {
"id": "firebase-uid",
"email": "user@dartmouth.edu",
"role": "faculty",
"dartmouthId": "d12345z"
},
"message": "Login successful"
}
GET /api/auth/me
Returns current authenticated user (requires authentication).
GET /api/auth/permissions
Returns user's effective permissions (role + group permissions).
Security Configuration
Signature Validation
The application validates SAML assertion signatures when SAML_IDP_CERT is configured:
{
wantAuthnResponseSigned: false, // Response envelope NOT signed
wantAssertionsSigned: true, // Assertion IS signed
signatureAlgorithm: 'sha256'
}
Important: Dartmouth signs the Assertion, not the Response envelope.
Clock Skew
A 2-minute clock skew tolerance is configured to handle timing differences:
acceptedClockSkewMs: 120000; // 2 minutes
Request Signing
AuthnRequests are signed when SP private key is configured:
signAuthnRequest: true;
Firebase Integration
Custom Claims
After SAML authentication, the following custom claims are set on the Firebase user:
{
role: 'faculty', // From SAML affiliation
dartmouthId: 'd12345z', // NetID
samlAuthenticated: true, // Flag for SAML users
affiliation: ['faculty'] // Raw affiliation values
}
Token Flow
- SAML Callback: Backend generates Firebase custom token
- Frontend: Exchanges custom token for ID token via
signInWithCustomToken() - Subsequent Requests: Frontend sends ID token in Authorization header
- Backend: Validates ID token via
verifyIdToken()
Frontend Implementation
SAML Callback Handler
The frontend handles the SAML callback at /auth/saml-callback:
// Extract token from URL
const urlParams = new URLSearchParams(window.location.search);
const customToken = urlParams.get('token');
// Exchange for Firebase session
await signInWithCustomToken(auth, customToken);
// Redirect to intended destination
const returnTo = localStorage.getItem('returnTo') || '/dashboard';
navigate(returnTo);
Auth Store
The Svelte auth store manages authentication state:
// Check authentication
$isAuthenticated // boolean
// Access user data
$authStore.user // { id, email, role, dartmouthId }
// Login with Dartmouth SSO
authStore.loginWithDartmouth(returnTo?: string)
// Logout
authStore.logout()
Registering with Dartmouth IT
To register the application with Dartmouth's IdP:
1. Generate SP Metadata
# Get metadata from running application
curl https://dartmouthpbs.org/api/auth/saml/metadata > sp-metadata.xml
2. Request IdP Registration
Contact Dartmouth IT with:
- SP Metadata XML
- Required attributes list
- Technical contact information
- Organization details
3. Configure IdP Certificate
After registration, add the Dartmouth IdP certificate to environment:
# Extract certificate from IdP metadata
SAML_IDP_CERT="-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgI...
-----END CERTIFICATE-----"
Troubleshooting
Common Issues
"SAML validation failed"
- Check clock synchronization between servers
- Verify IdP certificate is correctly configured
- Ensure callback URL matches registered ACS URL
"User not found after SAML login"
- Firebase may need to create the user
- Check Firebase project configuration
- Verify email attribute is present in assertion
"Signature validation failed"
- Confirm Dartmouth signs Assertion (not Response)
- Verify IdP certificate is current
- Check
wantAssertionsSignedsetting
Debug Logging
Enable SAML debug logging:
const log = createConsoleLogger('SAMLAuth');
// Logs validation results, attribute extraction, errors
Testing SAML Locally
For local development:
- Use Firebase emulator for user management
- Configure localhost callback URL in SP config
- Use ngrok or similar for IdP testing (requires HTTPS)
Security Best Practices
- Always validate signatures in production - Never disable
wantAssertionsSigned - Use HTTPS - SAML requires secure transport
- Protect private keys - Store SP keys securely, use file paths not inline
- Rotate certificates - Update SP certificates before expiration
- Monitor failed logins - Log and alert on validation failures
- Preserve admin roles - Never downgrade admin users from SAML