Skip to content

Commit ebdb053

Browse files
committed
Add WebAuthn implementation to SvelteKit
1 parent 2677fa4 commit ebdb053

File tree

2 files changed

+110
-0
lines changed

2 files changed

+110
-0
lines changed

packages/auth-sveltekit/src/client.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { BuiltinOAuthProviderNames } from "@edgedb/auth-core";
2+
import { WebAuthnClient } from "@edgedb/auth-core/webauthn";
23

34
export interface AuthOptions {
45
baseUrl: string;
@@ -35,10 +36,18 @@ export default function createClientAuth(options: AuthOptions) {
3536

3637
export class ClientAuth {
3738
protected readonly config: AuthConfig;
39+
readonly webAuthnClient: WebAuthnClient;
3840

3941
/** @internal */
4042
constructor(options: AuthOptions) {
4143
this.config = getConfig(options);
44+
this.webAuthnClient = new WebAuthnClient({
45+
signupOptionsUrl: `${this.config.authRoute}/webauthn/signup/options`,
46+
signupUrl: `${this.config.authRoute}/webauthn/signup`,
47+
signinOptionsUrl: `${this.config.authRoute}/webauthn/signin/options`,
48+
signinUrl: `${this.config.authRoute}/webauthn/signin`,
49+
verifyUrl: `${this.config.authRoute}/webauthn/verify`,
50+
});
4251
}
4352

4453
getOAuthUrl(providerName: BuiltinOAuthProviderNames) {

packages/auth-sveltekit/src/server.ts

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import {
1717
InvalidDataError,
1818
OAuthProviderFailureError,
1919
EdgeDBAuthError,
20+
RegistrationResponseJSON,
21+
AuthenticationResponseJSON,
2022
} from "@edgedb/auth-core";
2123
import {
2224
ClientAuth,
@@ -282,6 +284,45 @@ export class ServerRequestAuth extends ClientAuth {
282284
this.setVerifierCookie(verifier);
283285
}
284286

287+
async webAuthnSignUp(data: {
288+
email: string;
289+
credentials: RegistrationResponseJSON;
290+
verify_url: string;
291+
user_handle: string;
292+
}): Promise<{ tokenData: TokenData | null }> {
293+
const {
294+
email,
295+
credentials,
296+
verify_url: verifyUrl,
297+
user_handle: userHandle,
298+
} = data;
299+
300+
const result = await (
301+
await this.core
302+
).signupWithWebAuthn(email, credentials, verifyUrl, userHandle);
303+
304+
this.setVerifierCookie(result.verifier);
305+
if (result.status === "complete") {
306+
this.setAuthTokenCookie(result.tokenData.auth_token);
307+
return { tokenData: result.tokenData };
308+
}
309+
310+
return { tokenData: null };
311+
}
312+
313+
async webAuthnSignIn(data: {
314+
email: string;
315+
assertion: AuthenticationResponseJSON;
316+
}): Promise<{ tokenData: TokenData | null }> {
317+
const { email, assertion } = data;
318+
const tokenData = await (
319+
await this.core
320+
).signinWithWebAuthn(email, assertion);
321+
322+
this.setAuthTokenCookie(tokenData.auth_token);
323+
return { tokenData };
324+
}
325+
285326
async signout(): Promise<void> {
286327
this.deleteAuthTokenCookie();
287328
}
@@ -653,6 +694,66 @@ async function handleAuthRoutes(
653694
});
654695
}
655696

697+
case "webauthn/signup/options": {
698+
const email = searchParams.get("email");
699+
if (!email) {
700+
throw new InvalidDataError("email missing");
701+
}
702+
return redirect(302, (await core).getWebAuthnSignupOptionsUrl(email));
703+
}
704+
705+
case "webauthn/signin/options": {
706+
const email = searchParams.get("email");
707+
if (!email) {
708+
throw new InvalidDataError("email missing");
709+
}
710+
return redirect(302, (await core).getWebAuthnSigninOptionsUrl(email));
711+
}
712+
713+
case "webauthn/verify": {
714+
if (!onEmailVerify) {
715+
throw new ConfigurationError(
716+
`'onEmailVerify' auth route handler not configured`
717+
);
718+
}
719+
720+
const verificationToken = searchParams.get("verification_token");
721+
if (!verificationToken) {
722+
return onEmailVerify({
723+
error: new InvalidDataError("verification_token missing"),
724+
});
725+
}
726+
const verifier = cookies.get(config.pkceVerifierCookieName);
727+
if (!verifier) {
728+
return onEmailVerify({
729+
error: new PKCEError("no pkce verifier cookie found"),
730+
verificationToken,
731+
});
732+
}
733+
let tokenData: TokenData;
734+
try {
735+
tokenData = await (
736+
await core
737+
).verifyWebAuthnSignup(verificationToken, verifier);
738+
} catch (err) {
739+
return onEmailVerify({
740+
error: err instanceof Error ? err : new Error(String(err)),
741+
verificationToken,
742+
});
743+
}
744+
745+
cookies.set(config.authCookieName, tokenData.auth_token, {
746+
httpOnly: true,
747+
sameSite: "strict",
748+
path: "/",
749+
});
750+
751+
return onEmailVerify({
752+
error: null,
753+
tokenData,
754+
});
755+
}
756+
656757
case "signout": {
657758
if (!onSignout) {
658759
throw new ConfigurationError(

0 commit comments

Comments
 (0)