Verifying Credentials with mwen.io
@mwen/js-sdk is the consumer-facing SDK for integrating "Sign in with mwen.io" into your application. This page covers what mwen.io provides to apps, the trust model, and the core concepts you need before writing any code.
What your app gets from mwen.io
When a user signs in with mwen.io, your app receives:
- A pseudonymous user identifier — a
did:jwkstring that is stable for your app and cryptographically unique to it. Use it as a primary key for the user in your database. - Selectively disclosed claims — only the attributes the user consented to share (e.g.
given_name,age_over_18,email). No attribute the user withheld. - A cryptographic proof — an SD-JWT Verifiable Presentation that you can verify locally using standard JWT libraries. No call back to mwen.io.
- Access and refresh tokens — short-lived JWTs for session management.
What your app never receives
- The user's 24-word identity phrase or any private key material.
- Claims the user chose to hide or did not share.
- Information about which other apps the same user has signed in to.
- Linkable user data across apps — the
did:jwkyour app sees is cryptographically independent from what any other app sees.
This means you have zero KYC liability for identity data you never received.
The trust model
User's device Your app
──────────────────────────────────────────────────────────
BIP39 phrase Receives VP only
└── HKDF(phrase, "your-app.com")
└── P-256 private key User identifier:
└── did:jwk:...──────────did:jwk:... (stable)
└── SD-JWT-VC ────→ verify locally
(signed) (no mwen.io call)
Key properties:
- Self-issued: The user signs the credential with their own key. There is no mwen.io signing service in the path.
- Self-resolving: The
did:jwkidentifier encodes the public key directly. You extract the public key by base64url-decoding the DID string. No DNS lookup, no registry call. - Standards-based: SD-JWT-VC (IETF),
did:jwk(W3C). Any standards-compliant JWT library can verify the presentation.
Authentication path
The SDK currently supports Path A: extension-based authentication.
The user must have the mwen.io Chrome extension installed. Your app calls authenticate(), the extension popup opens for user consent, and the resolved credential is returned as a Promise — in under 2 seconds.
If the extension is not installed,
authenticate()throwsMwenExtensionNotFoundError. Handle this case by directing users to install the extension.
The user identifier: did:jwk
Your app's stable user identifier is the did:jwk in VerifiedSession.appIdentity (also the JWT sub claim). It looks like:
did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6Ii4uLiIsInkiOiIuLi4ifQ
This is a base64url-encoded JSON Web Key (P-256 public key). To extract the public key:
const suffix = did.replace('did:jwk:', '');
const jwk = JSON.parse(atob(suffix.replace(/-/g, '+').replace(/_/g, '/')));
// jwk = { kty: 'EC', crv: 'P-256', x: '...', y: '...' }
The SDK and its server utility handle this for you. You should treat did:jwk as an opaque stable string for database keying — no parsing required.
Package structure
The SDK ships three entry points:
| Import path | Purpose |
|---|---|
@mwen/js-sdk | Core types, MwenClient class, session utilities, error classes |
@mwen/js-sdk/react | MwenProvider context + useMwen() hook |
@mwen/js-sdk/server | verifyAccessTokenFromHeader() for Node.js API routes |