Minimal, zero-build 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.
Live Demo · API Reference · Examples
| 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 |
- Zero build step — Import from CDN or copy the file
- Single file — One
solid-oidc.js, nothing else - ~600 lines — 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
- Event-driven — React to session state changes
<!DOCTYPE html>
<html>
<head>
<title>Solid App</title>
</head>
<body>
<button id="login">Login</button>
<button id="logout" hidden>Logout</button>
<pre id="output"></pre>
<script type="module">
import { Session } from 'https://esm.sh/gh/JavaScriptSolidServer/solid-oidc/solid-oidc.js'
const session = new Session({
onStateChange: (e) => {
document.getElementById('login').hidden = e.detail.isActive
document.getElementById('logout').hidden = !e.detail.isActive
document.getElementById('output').textContent = e.detail.isActive
? `Logged in as ${e.detail.webId}`
: 'Not logged in'
}
})
// Try to restore previous session
session.restore().catch(() => {})
// Handle redirect from identity provider
session.handleRedirectFromLogin()
// Login button
document.getElementById('login').onclick = () => {
session.login('https://solidcommunity.net', window.location.href)
}
// Logout button
document.getElementById('logout').onclick = () => {
session.logout()
}
</script>
</body>
</html>That's it. No npm install, no webpack, no configuration.
import { Session } from 'https://esm.sh/gh/JavaScriptSolidServer/solid-oidc/solid-oidc.js'npm install solid-oidcimport { Session } from 'solid-oidc'Download solid-oidc.js and import it directly:
import { Session } from './solid-oidc.js'Create a new session instance.
const session = new Session({
// Optional: Pre-registered client_id (skips dynamic registration)
clientId: 'https://myapp.example/id',
// Optional: Custom database for session persistence
database: new SessionDatabase('my-app'),
// Optional: Event callbacks
onStateChange: (event) => console.log(event.detail),
onExpirationWarning: (event) => console.log('Expiring in', event.detail.expires_in),
onExpiration: () => console.log('Session expired')
})Redirect user to identity provider for authentication.
await session.login('https://solidcommunity.net', window.location.href)| Parameter | Description |
|---|---|
idp |
Identity provider URL (e.g., https://solidcommunity.net) |
redirectUri |
URL to redirect back to after login |
Handle the redirect from the identity provider. Call this on page load.
await session.handleRedirectFromLogin()Restore a previous session using stored refresh token.
try {
await session.restore()
console.log('Session restored')
} catch (error) {
console.log('No session to restore')
}End the session and clear all stored data.
await session.logout()Make an authenticated fetch request. Automatically includes DPoP proof and access token.
const response = await session.authFetch('https://pod.example/private/data.ttl')
const data = await response.text()Falls back to regular fetch() if no session is active.
| Property | Type | Description |
|---|---|---|
session.isActive |
boolean |
Whether user is logged in |
session.webId |
string | null |
User's WebID when logged in |
| Method | Returns | Description |
|---|---|---|
session.isExpired() |
boolean |
Whether access token is expired |
session.getExpiresIn() |
number |
Seconds until token expires (-1 if no token) |
The session extends EventTarget and emits these events:
| Event | Detail | Description |
|---|---|---|
sessionStateChange |
{ isActive, webId } |
Login/logout occurred |
sessionExpirationWarning |
{ expires_in } |
Token refresh failed but not expired |
sessionExpiration |
— | Token expired and refresh failed |
session.addEventListener('sessionStateChange', (event) => {
console.log('Active:', event.detail.isActive)
console.log('WebID:', event.detail.webId)
})import { Session, SessionDatabase } from './solid-oidc.js'
// Use a custom database name (useful for multiple sessions)
const database = new SessionDatabase('my-app-session')
const session = new Session({ database })Skip dynamic registration by providing your client ID:
const session = new Session({
clientId: 'https://myapp.example/id'
})const providers = [
{ name: 'Solid Community', url: 'https://solidcommunity.net' },
{ name: 'solidweb.org', url: 'https://solidweb.org' },
{ name: 'solidweb.me', url: 'https://solidweb.me' }
]
// Let user choose
const idp = prompt('Choose provider:', providers[0].url)
await session.login(idp, window.location.href)const session = new Session({
onExpirationWarning: async (event) => {
console.log(`Token expires in ${event.detail.expires_in}s, refreshing...`)
try {
await session.restore()
} catch {
if (confirm('Session expired. Login again?')) {
await session.login(idp, window.location.href)
}
}
}
})This library implements:
| Specification | Description |
|---|---|
| RFC 6749 | OAuth 2.0 |
| RFC 7636 | PKCE |
| RFC 9207 | Authorization Server Issuer Identification |
| RFC 9449 | DPoP (Demonstration of Proof-of-Possession) |
| Solid-OIDC | Solid OIDC Specification |
Open test.html in a browser to run the test suite:
npx serve .
# Visit http://localhost:3000/test.htmlTests cover:
- Session instantiation and state management
- SessionDatabase (IndexedDB) operations
- Event dispatching
| Requirement | Notes |
|---|---|
| ES Modules | <script type="module"> |
crypto.subtle |
Requires HTTPS or localhost |
indexedDB |
For session persistence |
Supported browsers: Chrome 63+, Firefox 57+, Safari 11+, Edge 79+
Based on solid-oidc-client-browser by uvdsl (Christoph Braun). Refactored into a minimal, zero-build, single-file library.