Passkeys NPM Package
A minimal TypeScript package for passkey management with configurable backend support. Use the default JAW passkey service or self-host your own compatible server.
Installation & Configuration
Basic Setup
import { configurePasskeys, createPasskey, loginWithPasskey } from '@jaw.id/passkeys'
configurePasskeys({
apiKey: 'your-justaname-api-key'
})
configurePasskeys({
serverUrl: 'https://your-passkey-server.com/api/passkeys'
})
Configuration Options
- mode:
'cross-platform'
(default) or 'app-specific'
cross-platform
: Uses a popup-based flow for passkey management across different applications
app-specific
: Uses native WebAuthn API directly for app-specific passkeys
- serverUrl: Backend server URL for passkey storage (defaults to JAW service)
- apiKey: Required when using JAW service, optional for custom servers
Passkey Modes
The package supports two modes for passkey creation and authentication:
Cross-Platform Mode (Default)
This is the default mode that allows passkeys to be shared across different applications:
- Uses a popup-based flow hosted at
keys.jaw.id
- Enables passkey portability between applications
- Ideal for multi-app ecosystems
configurePasskeys({
mode: 'cross-platform',
apiKey: 'your-api-key'
})
App-Specific Mode
For applications that need isolated passkey management:
- Uses native WebAuthn API directly
- Passkeys are specific to your application
- No popup or external dependencies
configurePasskeys({
mode: 'app-specific',
apiKey: 'your-api-key'
})
The package validates configuration automatically:
- JustaName service URLs require an API key
- Custom servers don't require an API key
Server Implementation Requirements
If you wish to run your own passkey server, it must implement the following endpoints and data structures to be compatible:
Endpoints
1. GET /
- Lookup Passkeys by Credential IDs
Retrieves passkey information for one or more credential IDs.
Query Parameters:
credentialIds
(string[], repeatable): Array of credential IDs to lookup
Response Format:
{
"statusCode": 200,
"result": {
"data": {
"passkeys": [
{
"credentialId": "string",
"publicKey": "0x...",
"displayName": "string"
}
]
},
"error": null
}
}
Note: publicKey
must be hex-encoded with 0x
prefix.
Error Response (404):
{
"statusCode": 404,
"result": {
"data": null,
"error": "Passkeys not found"
}
}
2. POST /
- Register a New Passkey
Registers a new passkey credential.
Request Body:
{
"credentialId": "string",
"publicKey": "0x...",
"displayName": "string"
}
Note: publicKey
must be hex-encoded with 0x
prefix.
Headers:
Content-Type: application/json
Response:
- Status 200/201 for successful registration
- Status 4xx/5xx for errors
Data Format Requirements
- Public Keys: Must be hex-encoded strings prefixed with
0x
- Credential IDs: Base64url-encoded strings (standard WebAuthn format)
- Display Names: Human-readable names for the passkeys
Security Considerations
When implementing your own passkey server:
- HTTPS Required: Always use HTTPS in production
- Authentication: Implement appropriate authentication if needed (API keys, OAuth, etc.)
- Data Validation: Validate all incoming credential IDs and public keys
- Storage: Securely store passkey data with appropriate encryption
- CORS: Configure CORS headers to allow requests from your application domain
Example Implementation (Node.js/Express)
const express = require('express');
const app = express();
const passkeys = new Map();
app.get('/api/passkeys', (req, res) => {
const credentialIds = req.query.credentialIds || [];
const ids = Array.isArray(credentialIds) ? credentialIds : [credentialIds];
const foundPasskeys = ids
.map(id => passkeys.get(id))
.filter(Boolean);
if (foundPasskeys.length === 0) {
return res.status(404).json({
statusCode: 404,
result: {
data: null,
error: 'Passkeys not found'
}
});
}
res.json({
statusCode: 200,
result: {
data: {
passkeys: foundPasskeys
},
error: null
}
});
});
app.post('/api/passkeys', express.json(), (req, res) => {
const { credentialId, publicKey, displayName } = req.body;
if (!credentialId || !publicKey || !displayName) {
return res.status(400).json({
statusCode: 400,
result: {
data: null,
error: 'Missing required fields'
}
});
}
passkeys.set(credentialId, {
credentialId,
publicKey,
displayName
});
res.status(201).send();
});
app.listen(3000);
API Usage
Creating a Passkey
import { createPasskey } from '@jaw.id/passkeys'
try {
const passkey = await createPasskey('My Passkey')
console.log('Created passkey:', passkey.id)
} catch (error) {
console.error('Failed to create passkey:', error)
}
Login with Passkey
import { loginWithPasskey, loginWithSpecificPasskey } from '@jaw.id/passkeys'
const passkey = await loginWithPasskey()
const specificPasskey = await loginWithSpecificPasskey('credential-id')
Local Storage Management
import {
checkAuth,
storePasskeyAccount,
fetchAccountsFromLocalStorage
} from '@jaw.id/passkeys'
const authResult = await checkAuth()
console.log('Is authenticated:', authResult.isAuthenticated)
const accounts = fetchAccountsFromLocalStorage()
Testing Your Implementation
To verify your server is compatible:
- Configure your server endpoint using
configurePasskeys()
- Test passkey registration through your application
- Test passkey authentication/login
- Verify that multiple credential ID lookups work correctly
Default Service
If no configuration is provided, the package defaults to the JAW passkey service at https://api.justaname.id/wallet/v2/passkeys
. When using the default service, you must provide an API key:
configurePasskeys({
apiKey: 'your-justaname-api-key'
})