From 1e46466f1d55f10b86d14c4589d8b1eedd3a89d0 Mon Sep 17 00:00:00 2001 From: Melvin Carvalho Date: Thu, 5 Mar 2026 07:47:54 +0100 Subject: [PATCH 1/6] Update README with comparison table and zero-dependency details Add detailed comparison with @inrupt/solid-client-authn-browser and @inrupt/oidc-client showing size, dependencies, and build requirements. Update badges and feature list for AGPL and Web Crypto. --- README.md | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index e7f0a8a..6c7f5dd 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@ # solid-oidc [![npm version](https://img.shields.io/npm/v/solid-oidc.svg)](https://www.npmjs.com/package/solid-oidc) -[![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) +[![license](https://img.shields.io/badge/license-AGPL--3.0-blue.svg)](LICENSE) [![zero dependencies](https://img.shields.io/badge/dependencies-0-brightgreen.svg)](#) +[![gzip size](https://img.shields.io/badge/gzip-4kb-brightgreen.svg)](#) -**Minimal, zero-build Solid-OIDC client for browsers.** +**Minimal, zero-build, zero-dependency Solid-OIDC client for browsers.** -A single JavaScript file (~600 lines) that handles the complete Solid-OIDC authentication flow. No bundler, no transpiler, no build step required. +A single JavaScript file (~700 lines, 4kb gzipped) that handles the complete Solid-OIDC authentication flow. No bundler, no transpiler, no build step, no npm dependencies. Uses the Web Crypto API natively. [**Live Demo**](https://javascriptsolidserver.github.io/solid-oidc/example.html) · [**API Reference**](#api-reference) · [**Examples**](#advanced-usage) @@ -14,18 +15,25 @@ A single JavaScript file (~600 lines) that handles the complete Solid-OIDC authe ## Why solid-oidc? -| Feature | solid-oidc | Other libraries | -|---------|------------|-----------------| -| **Size** | ~600 lines | 1,000–5,000 lines | -| **Build step** | None | Required | -| **Copy-paste ready** | Yes | No | -| **Readable source** | Yes | Compiled/minified | +| Feature | solid-oidc | @inrupt/solid-client-authn-browser | @inrupt/oidc-client | +|---------|------------|-----------------------------------|---------------------| +| **Unpacked size** | 34kb | 1,697kb | 18,704kb | +| **Gzipped** | 4kb | ~170kb | ~2MB | +| **Files in package** | 1 | 92 | 100+ | +| **Lines of code** | ~700 | thousands | thousands | +| **Dependencies** | 0 | 5 (+transitive) | 5 (+transitive) | +| **Build step** | None | Rollup | Required | +| **Copy-paste ready** | Yes | No | No | +| **Readable source** | Yes | Compiled/minified | Compiled | +| **Browser native crypto** | Yes (Web Crypto) | No (jose, uuid, events) | No (crypto-js, core-js) | +| **License** | AGPL-3.0 | MIT | MIT | ## Features +- **Zero dependencies** — Pure Web Crypto API, nothing to install - **Zero build step** — Import from CDN or copy the file - **Single file** — One `solid-oidc.js`, nothing else -- **~600 lines** — Readable, auditable, hackable +- **~700 lines, 4kb gzipped** — Readable, auditable, hackable - **Full Solid-OIDC** — Login, logout, token refresh, authenticated fetch - **DPoP bound tokens** — Secure proof-of-possession (RFC 9449) - **Persistent sessions** — Survives page refresh via IndexedDB @@ -282,6 +290,7 @@ This library implements: | [RFC 6749](https://tools.ietf.org/html/rfc6749) | OAuth 2.0 | | [RFC 7636](https://tools.ietf.org/html/rfc7636) | PKCE | | [RFC 9207](https://tools.ietf.org/html/rfc9207) | Authorization Server Issuer Identification | +| [RFC 7638](https://tools.ietf.org/html/rfc7638) | JWK Thumbprint | | [RFC 9449](https://tools.ietf.org/html/rfc9449) | DPoP (Demonstration of Proof-of-Possession) | | [Solid-OIDC](https://solidproject.org/TR/oidc) | Solid OIDC Specification | @@ -321,4 +330,4 @@ Based on [solid-oidc-client-browser](https://github.com/uvdsl/solid-oidc-client- ## License -[MIT](LICENSE) +[AGPL-3.0-or-later](LICENSE) From 5c5bfeb9f6447eb314d0c73d83899e19567bbc19 Mon Sep 17 00:00:00 2001 From: Melvin Carvalho Date: Thu, 5 Mar 2026 07:48:43 +0100 Subject: [PATCH 2/6] Bump version to 0.0.8 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 32f075d..6450634 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "solid-oidc", - "version": "0.0.7", + "version": "0.0.8", "description": "Minimal, zero-build, zero-dependency Solid-OIDC client for browsers", "type": "module", "main": "solid-oidc.js", From 26acec0a6817ee08c77522ebc5b87941aebfd4e0 Mon Sep 17 00:00:00 2001 From: Melvin Carvalho Date: Wed, 11 Mar 2026 01:57:09 +0100 Subject: [PATCH 3/6] Add SKILL.md for AI coding assistants --- SKILL.md | 270 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 270 insertions(+) create mode 100644 SKILL.md diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..ea81557 --- /dev/null +++ b/SKILL.md @@ -0,0 +1,270 @@ +# Solid-OIDC — Authentication for Solid Apps + +Authenticate users with Solid pods using Solid-OIDC. Zero dependencies, zero build step, single file. + +## What This Skill Does + +Implements the complete Solid-OIDC authentication flow in the browser: login, token refresh, logout, and authenticated fetch with DPoP proof-of-possession tokens. + +## Quick Start + +```html + +``` + +## Authentication Flow + +``` +1. session.login(idp, redirectUri) + ├── Discover OIDC config: GET {idp}/.well-known/openid-configuration + ├── Register client: POST registration_endpoint + ├── Generate PKCE: code_verifier + code_challenge (S256) + └── Redirect to: authorization_endpoint?response_type=code&... + +2. User authenticates at identity provider + +3. Redirect back with ?code=...&iss=...&state=... + +4. session.handleRedirectFromLogin() + ├── Verify issuer (RFC 9207) + ├── Verify CSRF state + ├── Generate DPoP key pair (ES256 / P-256) + ├── Exchange code for tokens: POST token_endpoint + ├── Validate access token (signature, issuer, audience, DPoP binding) + ├── Store refresh token in IndexedDB + └── Emit sessionStateChange event + +5. session.authFetch(url) + ├── Create DPoP proof (RFC 9449) + ├── Set Authorization: DPoP {access_token} + ├── Set DPoP: {proof_jwt} + └── fetch(url, { headers }) +``` + +## Session API + +### Constructor + +```js +const session = new Session({ + clientId: null, // Pre-registered client ID (skips dynamic registration) + database: new SessionDatabase(), // IndexedDB store for session persistence + onStateChange: (e) => {}, // { isActive, webId } + onExpirationWarning: (e) => {}, // { expires_in } + onExpiration: () => {} // Token expired, refresh failed +}) +``` + +### Methods + +| Method | Description | +|--------|-------------| +| `session.login(idp, redirectUri)` | Redirect to identity provider | +| `session.handleRedirectFromLogin()` | Handle redirect, exchange code for tokens | +| `session.restore()` | Restore session from stored refresh token | +| `session.logout()` | Clear session and stored data | +| `session.authFetch(url, options)` | Authenticated fetch with DPoP | + +### Properties + +| Property | Type | Description | +|----------|------|-------------| +| `session.isActive` | `boolean` | User is logged in | +| `session.webId` | `string` | User's WebID URI | +| `session.isExpired()` | `boolean` | Access token expired | +| `session.getExpiresIn()` | `number` | Seconds until expiry | + +### Events + +| Event | Detail | When | +|-------|--------|------| +| `sessionStateChange` | `{ isActive, webId }` | Login or logout | +| `sessionExpirationWarning` | `{ expires_in }` | Refresh failed, not yet expired | +| `sessionExpiration` | — | Expired and refresh failed | + +## OIDC Discovery + +Every Solid identity provider exposes: + +``` +GET {idp}/.well-known/openid-configuration +``` + +Key fields: + +| Field | Purpose | +|-------|---------| +| `issuer` | Canonical issuer URI | +| `authorization_endpoint` | Where to send user for login | +| `token_endpoint` | Where to exchange code for tokens | +| `registration_endpoint` | Dynamic client registration | +| `jwks_uri` | Public keys for token verification | + +## Dynamic Client Registration + +If no `clientId` is provided, the library registers automatically: + +```json +{ + "application_type": "web", + "redirect_uris": ["https://myapp.example/callback"], + "token_endpoint_auth_method": "none", + "grant_types": ["authorization_code", "refresh_token"], + "response_types": ["code"], + "scope": "openid offline_access webid" +} +``` + +## DPoP (Demonstration of Proof-of-Possession) + +Every authenticated request includes a DPoP proof JWT: + +``` +Authorization: DPoP {access_token} +DPoP: {proof_jwt} +``` + +The DPoP proof contains: + +| Claim | Value | +|-------|-------| +| `htu` | Target URL (origin + path) | +| `htm` | HTTP method (GET, PUT, etc.) | +| `iat` | Current timestamp | +| `jti` | Unique identifier | +| `ath` | Access token hash (when bound) | + +The access token's `cnf.jkt` claim must match the DPoP key's thumbprint (RFC 7638). + +## PKCE (Proof Key for Code Exchange) + +Prevents authorization code interception: + +| Parameter | Value | +|-----------|-------| +| `code_challenge_method` | `S256` | +| `code_challenge` | `BASE64URL(SHA256(code_verifier))` | +| `code_verifier` | Random string sent at token exchange | + +## Access Token Claims + +The Solid-OIDC access token contains: + +| Claim | Purpose | +|-------|---------| +| `webid` | User's WebID URI | +| `iss` | Issuer (identity provider) | +| `aud` | Audience (`solid`) | +| `exp` | Expiration timestamp | +| `iat` | Issued at timestamp | +| `client_id` | Registered client identifier | +| `cnf.jkt` | DPoP key thumbprint (proof-of-possession binding) | + +## Token Refresh + +Sessions persist across page reloads via IndexedDB: + +```js +// On page load, try to restore +try { + await session.restore() // Uses stored refresh_token +} catch { + // No session to restore +} +``` + +The refresh flow: +1. Read `refresh_token`, `token_endpoint`, `client_id`, `dpop_keypair` from IndexedDB +2. `POST token_endpoint` with `grant_type=refresh_token` +3. Store new refresh token +4. Update session state + +## Common Patterns + +### Login / Logout Buttons + +```js +document.getElementById('login').onclick = () => + session.login('https://solidcommunity.net', window.location.href) + +document.getElementById('logout').onclick = () => + session.logout() +``` + +### Read Private Data + +```js +const response = await session.authFetch('https://alice.pod/private/notes.ttl') +const data = await response.text() +``` + +### Write to Pod + +```js +await session.authFetch('https://alice.pod/public/hello.txt', { + method: 'PUT', + headers: { 'Content-Type': 'text/plain' }, + body: 'Hello, Solid!' +}) +``` + +### Multiple Identity Providers + +```js +const providers = [ + 'https://solidcommunity.net', + 'https://solidweb.org', + 'https://login.inrupt.com' +] +await session.login(providers[0], window.location.href) +``` + +## Installation + +```js +// CDN (recommended) +import { Session } from 'https://esm.sh/gh/JavaScriptSolidServer/solid-oidc/solid-oidc.js' + +// npm +import { Session } from 'solid-oidc' + +// Local copy +import { Session } from './solid-oidc.js' +``` + +## Browser Requirements + +| Requirement | Notes | +|-------------|-------| +| ES Modules | `