-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathauthentication-flow.ts
More file actions
118 lines (102 loc) · 3.08 KB
/
authentication-flow.ts
File metadata and controls
118 lines (102 loc) · 3.08 KB
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
import createWebAuth from "@sourceregistry/node-webauthn/server";
import {generateKeyPairSync} from "node:crypto";
const {privateKey, publicKey} = generateKeyPairSync("ec", {namedCurve: "P-256"});
const webauth = createWebAuth({
keyPair: {
kid: "main",
private_key: privateKey,
public_key: publicKey
},
issuer: "https://auth.example.com"
});
type StoredCredential = {
credential_id: string;
public_key: string;
counter: number;
};
type AuthenticationSession = {
challenge: string;
rp_id: string;
origin: string;
credential_id: string;
};
const credentials = new Map<string, StoredCredential>();
const authenticationSessions = new Map<string, AuthenticationSession>();
export const beginAuthentication = (userId: string) => {
const stored = credentials.get(userId);
if (!stored) {
throw new Error("Passkey not found");
}
const challenge = webauth.generateChallenge();
const rp_id = "example.com";
const origin = "https://example.com";
authenticationSessions.set(userId, {
challenge,
rp_id,
origin,
credential_id: stored.credential_id
});
return {
token: webauth.signAuthentication({
sub: userId,
challenge,
rp_id,
origin,
redirect_uri: "https://example.com/login",
credential_id: stored.credential_id
}),
publicKey: webauth.createAuthenticationOptions({
challenge,
rpId: rp_id,
allowCredentials: [
{
id: stored.credential_id,
type: "public-key" as const,
transports: ["internal"]
}
],
userVerification: "preferred" as const
})
};
};
export const finishAuthentication = (
userId: string,
token: string,
credential: {
client_data_json: string;
authenticator_data: string;
signature: string;
}
) => {
const session = authenticationSessions.get(userId);
const stored = credentials.get(userId);
if (!session || !stored) {
throw new Error("Authentication session not found");
}
const claims = webauth.verifyAuthentication(token);
if (claims.sub !== userId) {
throw new Error("Authentication token subject mismatch");
}
const result = webauth.verifyAuthenticationResponse({
expected_challenge: session.challenge,
client_data_json: credential.client_data_json,
authenticator_data: credential.authenticator_data,
signature: credential.signature,
origin: session.origin,
rp_id: session.rp_id,
public_key: stored.public_key,
previous_counter: stored.counter,
require_user_verification: false
});
credentials.set(userId, {
...stored,
counter: result.counter
});
authenticationSessions.delete(userId);
return {
user_id: userId,
authenticated: true,
user_verified: result.user_verified,
counter: result.counter
};
};