One-line DID login for any website. Drop a Web Component, get passwordless authentication backed by cryptographic identity. Falls back to Google/Microsoft OAuth and converts tokens into portable Verifiable Credentials.
@attestto/login gives your users decentralized identity login with zero friction. No passwords, no OAuth vendor lock-in, no monthly subscription. One HTML element, works in any page. Part of the Attestto identity infrastructure.
Documentation · Playground · Full API
<script type="module" src="https://unpkg.com/@attestto/login"></script>
<attestto-login></attestto-login>That's it. Users see a "Login with DID" button. If they have a DID wallet extension installed, they authenticate with cryptographic proof. No passwords.
npm install @attestto/loginimport '@attestto/login'
// Now use <attestto-login> in your templatesUsers without a DID wallet can still log in. Their OAuth token gets converted into a Verifiable Credential — next time, they use the credential directly.
<attestto-login
providers="google,microsoft"
issuer-endpoint="/api/auth/issue-vc"
trusted-issuers="did:web:yoursite.com"
></attestto-login>First login uses Google. Every login after uses their DID wallet. Google is cut out of the loop.
User clicks "Login with DID"
→ pickWallet() shows wallet selector
→ Wallet presents Verifiable Credential (CHAPI)
→ verifyPresentation() validates the trust chain
→ login-success event fires with user's DID
User clicks "Continue with Google" (fallback)
→ OAuth redirect to your backend
→ Backend verifies Google token
→ Backend issues a Verifiable Credential
→ VC stored in user's wallet
→ Next login uses DID path directly
| Attribute | Type | Default | Description |
|---|---|---|---|
providers |
string | "" |
Comma-separated OAuth providers: "google,microsoft" |
issuer-endpoint |
string | "" |
Backend URL for OAuth → VC exchange |
trusted-issuers |
string | "" |
Comma-separated trusted issuer DIDs |
resolver-url |
string | "" |
DID resolver URL for VP verification |
did-label |
string | "Login with DID" |
Primary button label |
theme |
"light" | "dark" |
"light" |
Color scheme |
compact |
boolean | false |
Button-only mode (no card wrapper) |
All events are composed — they cross Shadow DOM boundaries.
| Event | Detail | When |
|---|---|---|
login-success |
LoginResult |
Authentication succeeded |
login-error |
{ error, method } |
Authentication failed or cancelled |
oauth-start |
{ provider } |
User clicked an OAuth button |
wallets-discovered |
{ count, wallets } |
Wallet discovery completed |
const login = document.querySelector('attestto-login')
login.addEventListener('login-success', (e) => {
console.log('Authenticated:', e.detail.did)
console.log('Method:', e.detail.method) // 'did' or 'google'
// Create your session
fetch('/api/session', {
method: 'POST',
body: JSON.stringify({ did: e.detail.did })
})
})Override CSS custom properties to match your brand:
attestto-login {
--attestto-primary: #594FD3;
--attestto-primary-hover: #7B72ED;
--attestto-text: #1a1a2e;
--attestto-bg: #ffffff;
--attestto-bg-card: #f8fafc;
--attestto-border: #e2e8f0;
--attestto-font: system-ui, sans-serif;
}Style internal elements via CSS parts:
attestto-login::part(did-button) { border-radius: 20px; }
attestto-login::part(oauth-button) { font-size: 0.85rem; }
attestto-login::part(divider) { margin: 1.5rem 0; }@attestto/login re-exports the full @attestto/id-wallet-adapter API for advanced use cases:
import {
discoverWallets,
pickWallet,
verifyPresentation,
requestSignature,
} from '@attestto/login'
// Build your own login UI with full control
const wallets = await discoverWallets()
const wallet = await pickWallet()
const result = await verifyPresentation(vp, wallet, { resolverUrl, trustedIssuers })Your backend handles the OAuth callback and issues a Verifiable Credential. Example with Express:
app.get('/api/auth/issue-vc', async (req, res) => {
const { provider, redirect } = req.query
// 1. Run OAuth flow with the provider
const oauthResult = await runOAuth(provider)
// 2. Issue a VC proving the user authenticated
const vc = await issuer.issue({
type: 'AuthenticationCredential',
subjectDid: oauthResult.did || generateDid(),
claims: {
provider,
email: oauthResult.email,
verifiedAt: new Date().toISOString(),
},
})
// 3. Redirect back with the VC for wallet storage
res.redirect(`${redirect}#vc=${encodeCredential(vc)}`)
})| Package | Role |
|---|---|
@attestto/id-wallet-adapter |
Wallet discovery protocol (re-exported) |
@attestto-com/vc-sdk |
Issue VCs server-side (OAuth bridge) |
@attestto/verify |
Document verification + signing |
No tracking, no analytics, no telemetry. The component runs entirely client-side. OAuth tokens are exchanged via your own backend — Attestto never sees them. DID login never touches any server. See the Attestto privacy model.
Apache 2.0 — see LICENSE
One line of HTML. Passwordless login. No vendor lock-in.