Relying Party and Authenticator Roles
The foundation of modern passwordless identity relies on a strict cryptographic separation between the Relying Party and Authenticator Roles. In the Web Authentication (WebAuthn) specification, these two entities operate across distinct trust boundaries to eliminate shared secrets, mitigate phishing, and enable secure passkey provisioning. While the browser acts as a transport mediator, the actual credential generation, storage, and signing occur exclusively within hardware-backed or OS-managed authenticators. For a comprehensive overview of the underlying standards and protocol layering, review WebAuthn & FIDO2 Protocol Fundamentals before implementing role-specific workflows.
graph TD
Client[Browser / OS Client] -->|WebAuthn API| RP[Relying Party Server]
Client -->|CTAP2 / Platform API| Auth[Authenticator Hardware/OS]
Auth -->|Attestation/Assertion| Client
RP -->|Challenge/Options| Client
classDef rp fill:#e3f2fd,stroke:#1565c0;
classDef auth fill:#e8f5e9,stroke:#2e7d32;
class RP rp;
class Auth auth;
Figure 1: Trust boundary and message flow defining the Relying Party and Authenticator Roles in WebAuthn credential operations.
1. Defining the Core Entities in Modern Authentication
The WebAuthn specification enforces a strict separation of duties. The Relying Party (RP) represents the application or service requesting authentication, while the Authenticator is the cryptographic module that generates, stores, and uses asymmetric key pairs. Understanding this division is critical when mapping responsibilities across the FIDO2 stack: the browser/OS client handles API mediation, the CTAP2 authenticator executes cryptographic operations, and the RP server backend manages state and policy.
Validation & Implementation Workflows
- Map entity responsibilities: Align RP server logic with registration (
create) and authentication (get) phases. The RP never touches private key material. - Validate RP ID scoping: Ensure
rp.idmatches the effective domain and is validated againstwindow.location.origin. Subdomain scoping must be explicitly configured for multi-tenant deployments. - Confirm capability flags: Query
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()and parseauthenticatorSelectionrequirements before initiating credential creation.
Platform Execution Differences
| Execution Context | Mediation Layer | Key Storage |
|---|---|---|
| Browser-Mediated | WebAuthn JS API | Abstracted via OS Credential Manager |
| Platform-Bound | Secure Enclave / TPM | Non-exportable, device-locked |
| Cross-Platform Roaming | CTAP2 (USB/NFC/BLE) | Secure Element / Smart Card |
TypeScript Interface & RP ID Derivation
// Explicit type definition for PublicKeyCredentialCreationOptions
export interface WebAuthnRegistrationOptions {
rp: {
id: string;
name: string;
};
user: {
id: Uint8Array;
name: string;
displayName: string;
};
challenge: Uint8Array;
pubKeyCredParams: PublicKeyCredentialParameters[];
authenticatorSelection?: AuthenticatorSelectionCriteria;
attestation?: AttestationConveyancePreference;
timeout?: number;
}
// Derive RP ID safely from current origin
export function deriveRpId(): string {
const origin = window.location.origin;
const url = new URL(origin);
// Strip www. and subdomains per WebAuthn spec effective domain rules
const parts = url.hostname.split('.');
return parts.length > 2 ? parts.slice(-2).join('.') : url.hostname;
}
️ Implementation Pitfalls**
- Conflating the client browser with the authenticator hardware leads to incorrect UX assumptions about key portability.
- Assuming the RP server ever handles or stores private key material violates the core security model.
- Ignoring subdomain scoping rules causes credential isolation failures across staging/production environments.
Compliance Mapping: NIST SP 800-63B (AAL2/AAL3), ISO/IEC 27001 Annex A.9 (Access Control)
2. The Relying Party: Server-Side Responsibilities & State Management
The server-side component orchestrates cryptographic challenges and validates assertions without ever touching private keys. Architects designing cross-platform deployments should reference Understanding WebAuthn vs FIDO2 Architecture to align transport layers with RP expectations. The RP’s primary mandate is state management, cryptographic validation, and policy enforcement.
Core Server Workflows
- Generate 32-byte cryptographically secure challenge using
crypto.randomBytes(32). - Store pending challenge with a strict TTL (e.g., 300s) and bind it to the active session ID.
- Validate incoming assertion signature against the stored public key using COSE algorithm mapping.
- Verify credential counter monotonicity to detect cloned authenticators or replay attacks.
Node.js/Express Challenge Generation & Storage
import { Router, Request, Response } from 'express';
import crypto from 'crypto';
import { db } from './db'; // Abstracted DB client
const router = Router();
router.post('/webauthn/register/start', async (req: Request, res: Response) => {
const challenge = crypto.randomBytes(32);
const sessionId = req.sessionID;
const expiresAt = new Date(Date.now() + 300_000); // 5 min TTL
// Store pending challenge with strict expiration
await db.query(
'INSERT INTO pending_challenges (session_id, challenge, expires_at) VALUES ($1, $2, $3)',
[sessionId, challenge.toString('base64url'), expiresAt]
);
res.json({
challenge: challenge.toString('base64url'),
rp: { id: process.env.RP_ID, name: 'Identity Platform' },
user: { id: req.user.id, name: req.user.email, displayName: req.user.name },
pubKeyCredParams: [{ alg: -7, type: 'public-key' }, { alg: -257, type: 'public-key' }]
});
});
PostgreSQL Credential Schema
CREATE TABLE webauthn_credentials (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
credential_id BYTEA UNIQUE NOT NULL,
user_id UUID NOT NULL REFERENCES users(id),
public_key JSONB NOT NULL, -- Stores COSE key structure
sign_count INTEGER NOT NULL DEFAULT 0,
transports TEXT[] NOT NULL DEFAULT '{}',
backup_eligible BOOLEAN DEFAULT FALSE,
backup_state BOOLEAN DEFAULT FALSE,
created_at TIMESTAMPTZ DEFAULT NOW(),
revoked_at TIMESTAMPTZ
);
️ Implementation Pitfalls**
- Hardcoding RP ID across environments breaks origin binding and causes
NotAllowedError. - Failing to validate the signature algorithm against the stored COSE key enables downgrade attacks.
- Ignoring counter drift thresholds leads to false-positive replay blocks on legitimate roaming devices.
Compliance Mapping: SOC 2 Type II (Logical Access Controls), GDPR Article 5 (Data Minimization & Storage Limitation)
3. The Authenticator: Platform vs Cross-Platform Execution
Authenticators isolate cryptographic operations within hardware-backed secure elements, ensuring private keys never leave the device boundary. Implementation choices directly impact credential portability and recovery workflows. Authenticators execute via the Client to Authenticator Protocol 2 (CTAP2) and are mediated by OS-level credential managers.
Execution & Validation Steps
- Evaluate authenticator selection UI against
navigator.credentials.isConditionalMediationAvailable()and device capabilities. - Enforce user verification (UV) requirements (
requiredvspreferred) before key generation to meet phishing-resistance standards. - Handle resident key provisioning by setting
residentKey: 'required'and parsingdiscoverableflags in the response. - Parse and relay attestation objects to the RP for chain verification when enterprise compliance mandates hardware provenance.
Client-Side Configuration & Error Handling
async function createPasskey(challenge: string, userId: string) {
try {
const credential = await navigator.credentials.create({
publicKey: {
challenge: Uint8Array.from(atob(challenge), c => c.charCodeAt(0)),
rp: { id: window.location.hostname, name: 'Secure App' },
user: { id: Uint8Array.from(atob(userId), c => c.charCodeAt(0)), name: '[email protected]', displayName: 'User' },
pubKeyCredParams: [{ alg: -7, type: 'public-key' }],
authenticatorSelection: {
residentKey: 'required',
userVerification: 'required',
authenticatorAttachment: 'platform'
},
attestation: 'direct'
}
}) as PublicKeyCredential;
return {
id: credential.id,
rawId: btoa(String.fromCharCode(...new Uint8Array(credential.rawId))),
response: {
clientDataJSON: btoa(String.fromCharCode(...new Uint8Array(credential.response.clientDataJSON))),
attestationObject: btoa(String.fromCharCode(...new Uint8Array(credential.response.attestationObject)))
}
};
} catch (err) {
if (err instanceof DOMException) {
if (err.name === 'NotAllowedError') throw new Error('UV_BLOCKED: User verification denied or timed out.');
if (err.name === 'SecurityError') throw new Error('UV_NOT_ALLOWED: Origin mismatch or insecure context.');
}
throw err;
}
}
️ Implementation Pitfalls**
- Assuming UV is universally available on cross-platform roaming devices causes fallback failures.
- Over-relying on resident keys without server-bound fallback storage complicates account recovery.
- Ignoring transport filtering (
transports: ['internal', 'hybrid']) degrades UX on unsupported hardware.
Compliance Mapping: FIPS 140-3 (Cryptographic Module Validation), eIDAS (QSCD requirements for qualified signatures)
4. Credential Lifecycle: Registration & Assertion Workflows
During credential provisioning, the choice of asymmetric cryptography dictates long-term security posture. Compare implementation trade-offs in Public Key vs Symmetric Credential Types when designing fallback authentication paths. The lifecycle spans registration (attestation), authentication (assertion), and ongoing credential management.
Registration & Assertion Flow Validation
- Parse COSE key formats from
attestationObjectand map to algorithm allowlists (e.g., ES256-7, RS256-257). - Verify attestation chain against trusted root certificates if
attestation: 'direct'or'enterprise'is requested. - Validate
clientDataJSONhash matches the original challenge and verifytype: "webauthn.create"or"webauthn.get". - Enforce UP/UV flag checks in the authenticator data bitmask to confirm user presence and verification.
Server-Side Assertion Verification Middleware
import { verifyRegistrationResponse, verifyAuthenticationResponse } from '@simplewebauthn/server';
import { Request, Response, NextFunction } from 'express';
export const verifyAssertion = async (req: Request, res: Response, next: NextFunction) => {
const { credentialId, clientDataJSON, authenticatorData, signature } = req.body;
// Fetch stored credential from DB
const storedCred = await db.query('SELECT * FROM webauthn_credentials WHERE credential_id = $1', [credentialId]);
if (!storedCred) return res.status(404).json({ error: 'Credential not found' });
const verification = await verifyAuthenticationResponse({
response: {
id: credentialId,
rawId: credentialId,
response: {
clientDataJSON: clientDataJSON,
authenticatorData: authenticatorData,
signature: signature
}
},
expectedChallenge: req.session.challenge,
expectedOrigin: req.headers.origin!,
expectedRPID: process.env.RP_ID!,
credentialPublicKey: storedCred.public_key,
credentialCurrentSignCount: storedCred.sign_count,
requireUserVerification: true
});
if (verification.verified) {
// Update monotonic counter
await db.query('UPDATE webauthn_credentials SET sign_count = $1 WHERE credential_id = $2', [
verification.authenticationInfo.newCounter,
credentialId
]);
next();
} else {
res.status(401).json({ error: 'Assertion verification failed', reason: verification.error });
}
};
️ Implementation Pitfalls**
- Accepting
'none'attestation in high-security enterprise contexts violates hardware provenance requirements. - Failing to parse COSE key formats correctly across elliptic curves causes signature validation crashes.
- Not handling UP vs UV flags distinctly weakens phishing resistance guarantees.
Compliance Mapping: OWASP Authentication Cheat Sheet, PCI-DSS Requirement 8 (Multi-Factor Authentication)
5. Security Boundaries, Validation & Enterprise Compliance
Enterprise deployments require strict enforcement of cryptographic boundaries and audit trails. Implementing defense-in-depth controls is detailed in WebAuthn Security Boundaries for Enterprise Apps. The RP must act as the final gatekeeper, enforcing origin binding, algorithm agility, and counter monotonicity.
Validation & Enforcement Workflows
- Implement strict origin validation middleware to reject mismatched
clientDataJSON.originbefore challenge generation. - Log assertion failures with anonymized device metadata (
aaguid,transports) for threat intelligence and replay detection. - Enforce algorithm agility by maintaining an explicit allowlist and deprecating weak curves (e.g., restrict ES256K unless explicitly required by legacy hardware).
- Audit
backupEligibleandbackupStateflags to synchronize with cloud sync policies and comply with data residency requirements.
Counter Drift Detection & Structured Logging
import winston from 'winston';
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [new winston.transports.File({ filename: 'webauthn-audit.log' })]
});
export function validateCounter(currentCount: number, storedCount: number, tolerance: number = 5): boolean {
// Monotonicity check with configurable drift for roaming sync
if (currentCount <= storedCount) {
logger.warn({ event: 'counter_regression', current: currentCount, stored: storedCount });
return false; // Potential clone or replay
}
if (currentCount - storedCount > tolerance) {
logger.warn({ event: 'counter_drift_exceeded', current: currentCount, stored: storedCount });
// Allow but flag for manual review
}
return true;
}
️ Implementation Pitfalls**
- Disabling origin checks in staging environments and promoting to production exposes the RP to cross-site credential theft.
- Ignoring algorithm agility requirements leads to vendor lock-in and breaks interoperability with newer authenticators.
- Failing to log assertion failures obscures brute-force, credential stuffing, or replay attempts.
Compliance Mapping: NIST IR 8259-1 (IoT Device Cybersecurity), CISA Zero Trust Architecture (Identity Pillar), ISO/IEC 27017 (Cloud Security Controls)