Epic Stack with remix-auth-webauthn and remix-auth
This demonstrates how to use remix-auth-webauthn and remix-auth with the Epic Stack.
This example show you how you can create a new account with a passkey and login to your account with a passkey.
To check out the changes, check the git commit history. The important parts are:
-
Creation of a new model called Authenticator. It will contain the required information to retrieve the passkey.
prisma/scheam.prisma
-
Creation of a new provider called
webauthn.server.ts
. This provider implements the WebAuthnStrategy fromremix-auth-webauthn
. The most important part is in theverify
function. This function is used to register a new passkey and to authenticate an existing passkey. Upon registration, we create a newAuthenticator
entry in the database and link it to the user. -
The onboarding process is a little bit different with
passkey
and has to be modify. The start of the onboarding is the same as before, you verify your email and then you access the onboarding routes where you can create your account.To register your passkey, i added a link to a new page called
routes/_auth+/onboarding_.passkey.tsx
, the user can switch from using a password or a passkey.The loader for this file is a little bit complicated, we first have to call
await authenticator.authenticate(WEBAUTHN_PROVIDER_NAME, request)
But this call will throw and we need to catch it. It will contain a Response object of the following type
WebAuthnOptionsResponse
. Those information are required to start the passkey process, so we return them from our loader. (line 71 to 85)In the user interface, we are going to use that data when the user submit the form with its username and name. If the formData is valid (for example the username is available), then on the onSubmit event we will call a function named
handleFormSubmit
fromremix-auth-webauthn
like this :const [form, fields] = useForm({ ... onValidate({ formData }) { const result = parse(formData, { schema: SignupPasskeyFormSchema }) return result }, onSubmit(event) { handleFormSubmit(data, 'registration')(event) }, shouldRevalidate: 'onBlur', })
The
handleFormSubmit
will trigger the passkey process and upon success register your user.To finish the process, we need to save all the important part in the database.
In the
action
function if the form is valid we create a new session with the functionsignupWithWebauthn
(seeapp/utils/auth.server.ts
) and then callauthenticator.authenticate()
. -
The login process is very similar to the previous one. The
loader
is very similar with the one for registration.To start the authentication process, we are re-using the
ProviderConnectionForm
which create a login button for each connection. Like for the registration, we need to usehandleFormSubmit
fromremix-auth-webauthn
when we submit the form. Theaction
for this form is located in the routeauth.passkey.ts
where we will callauthenticator.authenticate()
.
This example is following the readme of remix-auth-webauthn.
Thanks to alexanderson1993 and kentcdodds for the help.