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