diff --git a/README.md b/README.md
index e7f0a8a..6c7f5dd 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,13 @@
# solid-oidc
[](https://www.npmjs.com/package/solid-oidc)
-[](LICENSE)
+[](LICENSE)
[](#)
+[](#)
-**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)
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 | `