Created
June 6, 2023 10:41
-
-
Save GrandSchtroumpf/49a5ff34d18ab77d58fd6063708228a8 to your computer and use it in GitHub Desktop.
Qwik and Webauthn
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { component$, event$, useStyles$ } from "@builder.io/qwik"; | |
import type { QwikSubmitEvent} from "@builder.io/qwik"; | |
import { server$, useNavigate } from "@builder.io/qwik-city"; | |
import { FormField, Label, Input } from "~/components/ui/form"; | |
import { startRegistration } from '@simplewebauthn/browser'; | |
import { generateRegistrationOptions, verifyRegistrationResponse } from "@simplewebauthn/server"; | |
import type { RegistrationResponseJSON } from '@simplewebauthn/typescript-types'; | |
import styles from './index.scss?inline'; | |
const rpName = 'Test Company'; | |
const rpID = 'localhost'; | |
const origin = 'http://localhost:5173'; | |
// Generate and store challenge for webauthn | |
const getRegistrationOptions = server$(async function (username: string) { | |
const options = generateRegistrationOptions({ | |
rpName, | |
rpID, | |
userID: crypto.randomUUID(), | |
userName: username, | |
attestationType: 'none', | |
}); | |
this.cookie.set('challenge', options.challenge) | |
return options; | |
}); | |
// Verify challenge signed by the client | |
const verifyRegistration = server$(async function (response: RegistrationResponseJSON) { | |
const expectedChallenge = this.cookie.get('challenge')?.value; | |
this.cookie.delete('challenge'); | |
if (!expectedChallenge) throw new Error('No challenge found'); | |
const verification = await verifyRegistrationResponse({ | |
response, | |
expectedChallenge, | |
expectedRPID: rpID, | |
expectedOrigin: origin | |
}); | |
if (verification.verified) { | |
// TODO: store verification.registrationInfo in DB | |
// TODO: store session | |
} | |
return verification.verified; | |
}) | |
export default component$(() => { | |
useStyles$(styles); | |
const nav = useNavigate(); | |
const signup = event$(async (event: QwikSubmitEvent, form: HTMLFormElement) => { | |
const data = new FormData(form); | |
const { username } = Object.fromEntries(data.entries()); | |
const options = await getRegistrationOptions(username.toString()); | |
const registration = await startRegistration(options); | |
const isVerified = await verifyRegistration(registration); | |
if (isVerified) nav('/profile'); | |
}); | |
return <form onSubmit$={signup} preventdefault:submit> | |
<label> | |
Username | |
<input placeholder="username" name="username"/> | |
</label> | |
<footer> | |
<button class="btn" type="reset">Cancel</button> | |
<button class="btn-fill primary" type="submit">Signup</button> | |
</footer> | |
</form> | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment