Skip to content

Add WebID-TLS authentication support #67

@melvincarvalho

Description

@melvincarvalho

Summary

Add WebID-TLS (mutual TLS / client certificate) authentication to JSS, enabling backend services, CLI tools, and automated agents to authenticate without user interaction.

Background

What is WebID-TLS?

WebID-TLS is a decentralized authentication protocol that uses TLS client certificates:

  1. Client presents X.509 certificate during TLS handshake
  2. Certificate's SubjectAlternativeName (SAN) contains a WebID URI
  3. Server fetches the WebID profile document
  4. Server verifies the certificate's public key matches the one published in the profile
sequenceDiagram
    participant C as Client
    participant S as JSS Server
    participant P as WebID Profile
    
    C->>S: TLS Handshake + Client Cert
    Note over S: Extract WebID from SAN
    S->>P: Fetch WebID Profile
    P-->>S: Profile with cert:key
    Note over S: Match modulus + exponent
    S-->>C: Authenticated as WebID
Loading

Why Now?

The W3C Linked Web Storage Use Cases group has raised this need for enterprise adoption:

"As a large tax advisory office, I want our existing backend services to authenticate themselves (without user interaction), such that these services can fetch and crunch private data from our clients."

Key drivers:

  • Enterprise adoption: Organizations with existing PKI/mTLS infrastructure want seamless LWS integration
  • Backend services: Server-to-server communication doesn't need browser UX
  • IoT/Agents: Automated systems need non-interactive authentication
  • CLI tools: Developers using curl or custom tools with client certs

Browser Support Note

While browser support for in-browser certificate generation (<keygen>) has been removed from Chrome and deprecated in Firefox, this does not affect server-side WebID-TLS:

  • Server-to-server: Works perfectly with standard mTLS
  • CLI tools: curl --cert works fine
  • Browser workarounds: YouID extension available

Implementation Guide

Files to Create/Modify

File Purpose
src/auth/webid-tls.js WebID-TLS authentication module
src/server.js Enable requestCert option on HTTPS server
src/config.js Add --webid-tls CLI flag

Step 1: Server Configuration

Modify src/server.js to request client certificates:

// In createServer, when SSL is enabled
if (options.ssl && options.ssl.key && options.ssl.cert) {
  const httpsOptions = {
    key: options.ssl.key,
    cert: options.ssl.cert,
    // Enable WebID-TLS
    requestCert: options.webidTls || false,  // Request client cert
    rejectUnauthorized: false  // Don't reject self-signed (we verify via WebID)
  };
  // ... create HTTPS server
}

Step 2: Create WebID-TLS Auth Module

Create src/auth/webid-tls.js:

import { graph, SPARQLToQuery } from 'rdflib';

const SPARQL_QUERY = `
  PREFIX cert: <http://www.w3.org/ns/auth/cert#>
  SELECT ?webid ?m ?e WHERE {
    ?webid cert:key ?key .
    ?key cert:modulus ?m .
    ?key cert:exponent ?e .
  }
`;

/**
 * Extract WebID from client certificate's SubjectAlternativeName
 */
export function getWebIdFromCert(certificate) {
  if (!certificate?.subjectaltname) return null;
  
  const match = certificate.subjectaltname.match(/URI:([^,\s]+)/);
  return match ? match[1] : null;
}

/**
 * Verify certificate public key matches WebID profile
 */
export async function verifyWebIdTls(certificate, webId) {
  if (!certificate.modulus || !certificate.exponent) {
    throw new Error('Certificate missing modulus or exponent');
  }
  
  // Fetch WebID profile
  const response = await fetch(webId, {
    headers: { 'Accept': 'text/turtle, application/ld+json' }
  });
  
  if (!response.ok) {
    throw new Error(`Failed to fetch WebID profile: ${response.status}`);
  }
  
  const body = await response.text();
  const contentType = response.headers.get('content-type')?.split(';')[0];
  
  // Parse profile and find matching key
  const g = graph();
  // ... parse RDF and run SPARQL query
  
  const certModulus = certificate.modulus.toLowerCase();
  const certExponent = parseInt(certificate.exponent, 16).toString();
  
  // Match modulus and exponent from profile
  // Return true if match found, false otherwise
}

/**
 * Middleware to authenticate via WebID-TLS
 */
export async function webIdTlsAuth(request) {
  const socket = request.raw?.socket || request.socket;
  
  if (!socket?.getPeerCertificate) {
    return null; // Not a TLS connection
  }
  
  const cert = socket.getPeerCertificate();
  if (!cert || Object.keys(cert).length === 0) {
    return null; // No client certificate
  }
  
  const webId = getWebIdFromCert(cert);
  if (!webId) {
    return null; // No WebID in certificate
  }
  
  const verified = await verifyWebIdTls(cert, webId);
  return verified ? webId : null;
}

Step 3: Integrate with Auth Flow

In src/auth/token.js, add WebID-TLS as authentication method:

import { webIdTlsAuth } from './webid-tls.js';

export async function getWebIdFromRequestAsync(request) {
  // Try bearer token first
  let webId = await getWebIdFromToken(request);
  
  // Fall back to WebID-TLS
  if (!webId) {
    webId = await webIdTlsAuth(request);
  }
  
  return { webId };
}

Step 4: CLI Configuration

Add to src/config.js:

.option('--webid-tls', 'Enable WebID-TLS client certificate authentication')

Testing

Manual Testing with curl

# Generate self-signed cert with WebID in SAN
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 \
  -subj "/CN=Test" -addext "subjectAltName=URI:https://example.com/profile/card#me"

# Test authentication
curl --cert cert.pem --key key.pem https://localhost:8443/private/resource

Unit Tests

describe('WebID-TLS Authentication', () => {
  it('should extract WebID from certificate SAN', () => {
    const cert = { subjectaltname: 'URI:https://alice.example/card#me' };
    expect(getWebIdFromCert(cert)).toBe('https://alice.example/card#me');
  });
  
  it('should verify certificate against WebID profile', async () => {
    // Mock profile with matching cert:key
    // Verify returns true
  });
  
  it('should reject mismatched certificate', async () => {
    // Mock profile with different key
    // Verify returns false
  });
});

WebID Profile Requirements

For WebID-TLS to work, the WebID profile must contain the certificate's public key:

@prefix cert: <http://www.w3.org/ns/auth/cert#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

<#me> cert:key [
    a cert:RSAPublicKey;
    cert:modulus "abc123..."^^xsd:hexBinary;
    cert:exponent 65537
] .

Use Cases Enabled

  1. Backend Services: Tax advisors fetching client data (W3C use case)
  2. CLI Tools: Developers using curl or custom scripts
  3. IoT Devices: Sensors/actuators with embedded certificates
  4. Automated Agents: Bots and crawlers with verifiable identity
  5. Legacy Integration: Organizations with existing PKI infrastructure

References

Related

  • Solid-OIDC (current default auth) remains primary for browser-based apps
  • WebID-TLS would be an additional authentication method, not a replacement

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions