Skip to content

Instantly share code, notes, and snippets.

@GrandSchtroumpf
Created June 6, 2023 10:41
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save GrandSchtroumpf/49a5ff34d18ab77d58fd6063708228a8 to your computer and use it in GitHub Desktop.
Save GrandSchtroumpf/49a5ff34d18ab77d58fd6063708228a8 to your computer and use it in GitHub Desktop.
Qwik and Webauthn
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