Skip to main content

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:

  1. User clicks "Sign In with Dartmouth"
  2. Backend redirects to Dartmouth SAML IdP
  3. User authenticates with Dartmouth credentials
  4. IdP posts SAML assertion back to backend
  5. Backend validates assertion, creates/updates Firebase user
  6. Backend generates Firebase custom token
  7. 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):

SettingValue
Entity IDhttps://dartmouthpbs.org
ACS URLhttps://dartmouthpbs.org/api/auth/saml/callback
NameID Formaturn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
SigningSHA-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:

AttributeOIDDescription
eduPersonPrincipalName1.3.6.1.4.1.5923.1.1.1.6Primary identifier (NetID@dartmouth.edu)
mail0.9.2342.19200300.100.1.3Email address
givenName2.5.4.42First name
sn2.5.4.4Last name (surname)
displayName2.16.840.1.113730.3.1.241Display name
eduPersonAffiliation1.3.6.1.4.1.5923.1.1.1.1Affiliation (faculty, staff, student)
ou2.5.4.11Organizational 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:

AffiliationAssigned Role
faculty, employeefaculty
staffstaff
studentstudent
alumuser
(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:

  1. Validates SAML assertion signature (when IdP cert configured)
  2. Extracts user attributes
  3. Creates or updates Firebase user
  4. Generates Firebase custom token
  5. 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

  1. SAML Callback: Backend generates Firebase custom token
  2. Frontend: Exchanges custom token for ID token via signInWithCustomToken()
  3. Subsequent Requests: Frontend sends ID token in Authorization header
  4. 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 wantAssertionsSigned setting

Debug Logging

Enable SAML debug logging:

const log = createConsoleLogger('SAMLAuth');
// Logs validation results, attribute extraction, errors

Testing SAML Locally

For local development:

  1. Use Firebase emulator for user management
  2. Configure localhost callback URL in SP config
  3. Use ngrok or similar for IdP testing (requires HTTPS)

Security Best Practices

  1. Always validate signatures in production - Never disable wantAssertionsSigned
  2. Use HTTPS - SAML requires secure transport
  3. Protect private keys - Store SP keys securely, use file paths not inline
  4. Rotate certificates - Update SP certificates before expiration
  5. Monitor failed logins - Log and alert on validation failures
  6. Preserve admin roles - Never downgrade admin users from SAML