Skip to main content

Verify email ownership

Passlock can handle the routine task of verifying email ownership by sending a verification link or code.

tip

You can customise the verification email and even add your own logo. Please see the emails documentation.

Passlock can generate a secure link and email it to the user as part of the registration process. Whilst this may seem a trivial task, it's actually a bit more complex than it first appears.

Note: Passlock will generate secure links to your domain, not passlock.dev:

  1. When registering a passkey, pass the verifyEmail option
  2. Passlock generates a secure link to your domain, appends ?code={code} and emails it to the recipient
  3. The recipient clicks the link and hits your url
  4. Your frontend calls verifyEmailLink
  5. verifyEmailLink extracts the code from the browser url and sends it to Passlock for verification
register.ts
import { Passlock } from '@passlock/client'

const passlock = new Passlock({ tenancyId, clientId })

await passlock.registerPasskey({
...
verifyEmail: {
method: 'link',
// Note: this url must comply with your RP ID
redirectUrl: 'https://mydomain.com/verify'
}
})
verify.ts
// this will pull the ?code=123 from the current window.location
// and send it to the Passlock backend for verification
const result = await passlock.verifyEmailLink()

Why do it this way?

In short, because we need to re-authenticate the user.

Via a verification code

Verify similar to sending a link. Passlock will email a 6 digit code to the mailbox owner. You should prompt the user to enter the code and call the verifyEmailCode function.

register.ts
await passlock.registerPasskey({ 
...
verifyEmail: {
method: 'code'
}
})
verify.ts
// Prompt the user to enter the code and call
const result = await passlock.verifyEmailCode({ code })

When should you choose to send a link vs a code? It depends on the complexity of your registration flow. If you have transient state in your frontend which is not immune to a page refresh, the code is a better option.

Also, Apple recently announced autofill of mailbox verification code (they've done something similar for SMS codes for a while). Assuming the user is using the latest Safari and Mail apps, Safari will scan the mailbox and autofill any codes that were sent.

Re-authenticating the user

Why do we redirect the user to your site, then ask you to send us the code? Why not send a link to passlock.dev, check the link and then redirect them to your site? We do it for two reasons: Firstly aesthetics - a user will feel more comfortable clicking a link to your domain than ours. Secondly there is an important security aspect at play:

Verifying that someone has access to the email mailbox is not enough. We also need to ensure that the person verifying the email is the same person who registered the passkey. Otherwise you would be vulnerable to this scenario:

  1. Mallory registers an account using the email alice@gmail.com
  2. We send a verification email to alice@gmail.com
  3. Alice (who receives far too many emails) accidently clicks the link
  4. We have now confimed that Mallory owns alice@gmail.com 😱
The implementation

During the registerPasskey call, Passlock will generate a secure token (the one you send to your backend for verification). We also store this in local storage. When you call verifyEmailLink we look for this token in local storage. If we can't find it we reauthenticate the user to obtain a fresh token.

We then send both the email verification code and the token to the backend. Our backend verifies that the code is valid and relates to the same account as the authentication token.

If your're worried about us storing a credential in local storage, we clear it during the verifyEmailLink call. You can also call clearExpiredTokens to delete any tokens from local storage. In addition, tokens are only valid for 5 minutes.