[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/JavaScriptSolidServer/solid-oidc/gh-pages/SKILL.md [Back]  [Original]

# 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 | `` |
| `crypto.subtle` | Requires HTTPS or localhost |
| `indexedDB` | Session persistence |

## Specifications

| Spec | Description |
|------|-------------|
| [Solid-OIDC](https://solidproject.org/TR/oidc) | Solid authentication |
| [RFC 6749](https://tools.ietf.org/html/rfc6749) | OAuth 2.0 |
| [RFC 7636](https://tools.ietf.org/html/rfc7636) | PKCE |
| [RFC 7638](https://tools.ietf.org/html/rfc7638) | JWK Thumbprint |
| [RFC 9207](https://tools.ietf.org/html/rfc9207) | Authorization Server Issuer Identification |
| [RFC 9449](https://tools.ietf.org/html/rfc9449) | DPoP |

## License

AGPL-3.0

Web Proxy Viewer  |  New URL  |  Original Page