Skip to content

Commit 4b1bf1e

Browse files
Merge pull request #388 from pimlicolabs/feat/add=auth
Add authentication flow for passkey
2 parents 7b4357a + 9448c6f commit 4b1bf1e

File tree

7 files changed

+280
-50
lines changed

7 files changed

+280
-50
lines changed

.changeset/quiet-sloths-lose.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"permissionless": patch
3+
---
4+
5+
Added passkeys authentication flow
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { Base64 } from "ox"
2+
import type { WebAuthnP256 } from "ox"
3+
import {
4+
type Account,
5+
type Chain,
6+
type Client,
7+
type Transport,
8+
toHex
9+
} from "viem"
10+
import type { PasskeyServerRpcSchema } from "../../types/passkeyServer.js"
11+
12+
export type StartAuthenticationReturnType = WebAuthnP256.sign.Options & {
13+
uuid: string
14+
}
15+
16+
export const startAuthentication = async (
17+
client: Client<
18+
Transport,
19+
Chain | undefined,
20+
Account | undefined,
21+
PasskeyServerRpcSchema
22+
>
23+
): Promise<StartAuthenticationReturnType> => {
24+
const response = await client.request({
25+
method: "pks_startAuthentication",
26+
params: []
27+
})
28+
29+
return {
30+
challenge: toHex(Base64.toBytes(response.challenge)),
31+
rpId: response.rpId,
32+
userVerification: response.userVerification,
33+
uuid: response.uuid
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { Base64, type WebAuthnP256 } from "ox"
2+
// import { Base64 } from "ox"
3+
import type { Account, Chain, Client, Hex, Transport } from "viem"
4+
import type { PasskeyServerRpcSchema } from "../../types/passkeyServer.js"
5+
6+
export type VerifyAuthenticationParameters = WebAuthnP256.sign.ReturnType & {
7+
uuid: string
8+
}
9+
10+
export type VerifyAuthenticationReturnType = {
11+
success: boolean
12+
id: string
13+
publicKey: Hex
14+
}
15+
16+
export const verifyAuthentication = async (
17+
client: Client<
18+
Transport,
19+
Chain | undefined,
20+
Account | undefined,
21+
PasskeyServerRpcSchema
22+
>,
23+
args: VerifyAuthenticationParameters
24+
): Promise<VerifyAuthenticationReturnType> => {
25+
const { raw, uuid } = args
26+
27+
let responseAuthenticatorData: string
28+
29+
if ("authenticatorData" in raw.response) {
30+
responseAuthenticatorData = Base64.fromBytes(
31+
new Uint8Array(raw.response.authenticatorData as ArrayBuffer),
32+
{
33+
url: true
34+
}
35+
)
36+
} else {
37+
throw new Error("authenticatorData not found in the signature")
38+
}
39+
40+
let signature: string
41+
if ("signature" in raw.response) {
42+
signature = Base64.fromBytes(
43+
new Uint8Array(raw.response.signature as ArrayBuffer),
44+
{
45+
pad: false,
46+
url: true
47+
}
48+
)
49+
} else {
50+
throw new Error("signature not found in the signature")
51+
}
52+
53+
let userHandle: string | undefined
54+
if ("userHandle" in raw.response) {
55+
userHandle = Base64.fromBytes(
56+
new Uint8Array(raw.response.userHandle as ArrayBuffer),
57+
{
58+
pad: false,
59+
url: true
60+
}
61+
)
62+
}
63+
64+
const serverResponse = await client.request(
65+
{
66+
method: "pks_verifyAuthentication",
67+
params: [
68+
{
69+
id: raw.id,
70+
rawId: Base64.fromBytes(new Uint8Array(raw.rawId), {
71+
pad: false,
72+
url: true
73+
}),
74+
authenticatorAttachment: raw.authenticatorAttachment as
75+
| "cross-platform"
76+
| "platform",
77+
response: {
78+
clientDataJSON: Base64.fromBytes(
79+
new Uint8Array(raw.response.clientDataJSON),
80+
{
81+
pad: false,
82+
url: true
83+
}
84+
),
85+
authenticatorData: responseAuthenticatorData,
86+
signature,
87+
userHandle
88+
},
89+
clientExtensionResults: raw.getClientExtensionResults(),
90+
type: raw.type as "public-key"
91+
},
92+
{
93+
uuid
94+
}
95+
]
96+
},
97+
{
98+
retryCount: 0
99+
}
100+
)
101+
102+
const success = Boolean(serverResponse?.success)
103+
const id = serverResponse?.id
104+
const publicKey = serverResponse?.publicKey
105+
106+
if (typeof id !== "string") {
107+
throw new Error("Invalid passkey id returned from server")
108+
}
109+
110+
if (typeof publicKey !== "string" || !publicKey.startsWith("0x")) {
111+
throw new Error(
112+
"Invalid public key returned from server - must be hex string starting with 0x"
113+
)
114+
}
115+
116+
return {
117+
success,
118+
id,
119+
publicKey: publicKey as Hex
120+
}
121+
}

packages/permissionless/actions/passkeyServer/verifyRegistration.ts

+48-38
Original file line numberDiff line numberDiff line change
@@ -48,49 +48,59 @@ export const verifyRegistration = async (
4848
}
4949
}
5050

51-
const serverResponse = await client.request({
52-
method: "pks_verifyRegistration",
53-
params: [
54-
{
55-
id: credential.id,
56-
rawId: Base64.fromBytes(new Uint8Array(credential.raw.rawId), {
57-
pad: false,
58-
url: true
59-
}),
60-
response: {
61-
clientDataJSON: Base64.fromBytes(
62-
new Uint8Array(response.clientDataJSON)
63-
),
64-
attestationObject: Base64.fromBytes(
65-
new Uint8Array(response.attestationObject),
51+
const serverResponse = await client.request(
52+
{
53+
method: "pks_verifyRegistration",
54+
params: [
55+
{
56+
id: credential.id,
57+
rawId: Base64.fromBytes(
58+
new Uint8Array(credential.raw.rawId),
6659
{
60+
pad: false,
6761
url: true
6862
}
6963
),
70-
transports:
71-
typeof response.getTransports === "function"
72-
? (response.getTransports() as (
73-
| "ble"
74-
| "cable"
75-
| "hybrid"
76-
| "internal"
77-
| "nfc"
78-
| "smart-card"
79-
| "usb"
80-
)[])
81-
: undefined,
82-
publicKeyAlgorithm: responsePublicKeyAlgorithm,
83-
authenticatorData: responseAuthenticatorData
64+
response: {
65+
clientDataJSON: Base64.fromBytes(
66+
new Uint8Array(response.clientDataJSON)
67+
),
68+
attestationObject: Base64.fromBytes(
69+
new Uint8Array(response.attestationObject),
70+
{
71+
url: true
72+
}
73+
),
74+
transports:
75+
typeof response.getTransports === "function"
76+
? (response.getTransports() as (
77+
| "ble"
78+
| "cable"
79+
| "hybrid"
80+
| "internal"
81+
| "nfc"
82+
| "smart-card"
83+
| "usb"
84+
)[])
85+
: undefined,
86+
publicKeyAlgorithm: responsePublicKeyAlgorithm,
87+
authenticatorData: responseAuthenticatorData
88+
},
89+
authenticatorAttachment: credential.raw
90+
.authenticatorAttachment as
91+
| "cross-platform"
92+
| "platform",
93+
clientExtensionResults:
94+
credential.raw.getClientExtensionResults(),
95+
type: credential.raw.type as "public-key"
8496
},
85-
authenticatorAttachment: credential.raw
86-
.authenticatorAttachment as "cross-platform" | "platform",
87-
clientExtensionResults:
88-
credential.raw.getClientExtensionResults(),
89-
type: credential.raw.type as "public-key"
90-
},
91-
context
92-
]
93-
})
97+
context
98+
]
99+
},
100+
{
101+
retryCount: 0
102+
}
103+
)
94104

95105
const success = Boolean(serverResponse?.success)
96106
const id = serverResponse?.id

packages/permissionless/clients/decorators/passkeyServer.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,20 @@ import {
44
type GetCredentialsReturnType,
55
getCredentials
66
} from "../../actions/passkeyServer/getCredentials.js"
7+
import {
8+
type StartAuthenticationReturnType,
9+
startAuthentication
10+
} from "../../actions/passkeyServer/startAuthentication.js"
711
import {
812
type StartRegistrationParameters,
913
type StartRegistrationReturnType,
1014
startRegistration
1115
} from "../../actions/passkeyServer/startRegistration.js"
16+
import {
17+
type VerifyAuthenticationParameters,
18+
type VerifyAuthenticationReturnType,
19+
verifyAuthentication
20+
} from "../../actions/passkeyServer/verifyAuthentication.js"
1221
import {
1322
type VerifyRegistrationParameters,
1423
type VerifyRegistrationReturnType,
@@ -23,6 +32,10 @@ export type PasskeyServerActions = {
2332
verifyRegistration: (
2433
args: VerifyRegistrationParameters
2534
) => Promise<VerifyRegistrationReturnType>
35+
startAuthentication: () => Promise<StartAuthenticationReturnType>
36+
verifyAuthentication: (
37+
args: VerifyAuthenticationParameters
38+
) => Promise<VerifyAuthenticationReturnType>
2639
getCredentials: (
2740
args: GetCredentialsParameters
2841
) => Promise<GetCredentialsReturnType>
@@ -38,5 +51,7 @@ export const passkeyServerActions = (
3851
): PasskeyServerActions => ({
3952
startRegistration: (args) => startRegistration(client, args),
4053
verifyRegistration: (args) => verifyRegistration(client, args),
41-
getCredentials: (args) => getCredentials(client, args)
54+
getCredentials: (args) => getCredentials(client, args),
55+
startAuthentication: () => startAuthentication(client),
56+
verifyAuthentication: (args) => verifyAuthentication(client, args)
4257
})

packages/permissionless/types/passkeyServer.ts

+41
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
import type { Hex } from "viem"
22

33
export type PasskeyServerRpcSchema = [
4+
{
5+
Method: "pks_startAuthentication"
6+
Parameters: []
7+
ReturnType: {
8+
challenge: string
9+
rpId: string
10+
timeout?: number
11+
userVerification?: "required" | "preferred" | "discouraged"
12+
uuid: string
13+
}
14+
},
415
{
516
Method: "pks_startRegistration"
617
Parameters: [context: unknown]
@@ -71,6 +82,36 @@ export type PasskeyServerRpcSchema = [
7182
publicKey: Hex
7283
}
7384
},
85+
{
86+
Method: "pks_verifyAuthentication"
87+
Parameters: [
88+
{
89+
id: string
90+
rawId: string
91+
response: {
92+
clientDataJSON: string
93+
authenticatorData: string
94+
signature: string
95+
userHandle?: string
96+
}
97+
authenticatorAttachment: "cross-platform" | "platform"
98+
clientExtensionResults: {
99+
appid?: boolean
100+
credProps?: {
101+
rk?: boolean
102+
}
103+
hmacCreateSecret?: boolean
104+
}
105+
type: "public-key"
106+
},
107+
context: unknown
108+
]
109+
ReturnType: {
110+
success: boolean
111+
id: string
112+
publicKey: Hex
113+
}
114+
},
74115
{
75116
Method: "pks_getCredentials"
76117
Parameters: [context: unknown]

0 commit comments

Comments
 (0)