1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
const PASSKEY_CREDENTIAL_ID_KEY = "urbit_wallet_passkey_id";
const RELYING_PARTY_ID = "wallet.urbit.org"; // Change this to your domain
const RELYING_PARTY_NAME = "Urbit Wallet";
async function createPasskey() {
const pkok =
await window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
console.log({ pkok });
// if (Platform.OS !== "web") {
// return false;
// }
console.log("creating passkey");
try {
// Generate a random user ID
const userId = new Uint8Array(16);
crypto.getRandomValues(userId);
// Generate a random challenge
const challenge = new Uint8Array(32);
crypto.getRandomValues(challenge);
const createCredentialOptions: CredentialCreationOptions = {
publicKey: {
// REQUIRED: Random challenge to prevent replay attacks
// Must be at least 16 bytes, we use 32 for extra security
challenge,
// REQUIRED: Relying Party (your website/app)
rp: {
// REQUIRED: Human-readable name shown in UI prompts
name: RELYING_PARTY_NAME,
// OPTIONAL: Domain that owns this credential
// If omitted, defaults to current domain
// Must match or be a registrable suffix of the current domain
id:
window.location.hostname === "localhost"
? "localhost"
: RELYING_PARTY_ID,
},
// REQUIRED: User information
user: {
// REQUIRED: Unique user ID (must not contain PII)
// Used by authenticator to distinguish between accounts
id: userId,
// REQUIRED: Unique username/identifier for this RP
// Shown in some UI contexts, should be recognizable to user
name: "urbit-user",
// REQUIRED: Human-readable name for display
// Shown in account selectors and prompts
displayName: "Urbit Wallet User",
},
// REQUIRED: List of acceptable public key algorithms
// Order matters - authenticator will use first supported algorithm
pubKeyCredParams: [
{ alg: -7, type: "public-key" }, // ES256 (ECDSA with SHA-256) - most common
{ alg: -257, type: "public-key" }, // RS256 (RSASSA-PKCS1-v1_5) - fallback
],
// OPTIONAL: Criteria for authenticator selection
authenticatorSelection: {
// OPTIONAL: "platform" (built-in like Touch ID) or "cross-platform" (USB key)
// Omitting allows both types
// authenticatorAttachment: "platform",
// OPTIONAL: User verification requirement
// "required" - must verify user (biometric/PIN)
// "preferred" - verify if possible, continue if not (default)
// "discouraged" - don't verify unless required by RP
userVerification: "preferred",
// OPTIONAL: Whether to create a discoverable credential (passkey)
// "required" - must create discoverable credential
// "preferred" - create if possible (default)
// "discouraged" - don't create discoverable credential
residentKey: "preferred",
// DEPRECATED: Use residentKey instead
// Kept for backwards compatibility with older authenticators
requireResidentKey: false,
},
// OPTIONAL: Time limit in milliseconds (default varies by browser)
// 60 seconds is reasonable for user interaction
timeout: 60000,
// OPTIONAL: Attestation preference (proof of authenticator legitimacy)
// "none" - no attestation needed (default, best for privacy)
// "indirect" - RP prefers attestation but allows anonymization
// "direct" - RP needs attestation from authenticator
// "enterprise" - RP needs attestation and device info (requires permission)
attestation: "none",
// OPTIONAL: List of credentials to exclude (prevent duplicates)
// excludeCredentials: [
// { id: existingCredentialId, type: "public-key" }
// ],
// OPTIONAL: Extensions for additional features
// extensions: {
// credProps: true, // Request additional credential properties
// hmacCreateSecret: true, // For symmetric key operations
// },
},
};
const credential = (await navigator.credentials.create(
createCredentialOptions,
)) as PublicKeyCredential;
console.log({ credential });
if (credential) {
// Store the credential ID for later use
// await SecureStore.setItemAsync(PASSKEY_CREDENTIAL_ID_KEY, credential.id);
console.log("Passkey created successfully", credential.id);
return true;
}
return false;
} catch (error) {
console.error("Error creating passkey:", error);
return false;
}
}
|