Skip to content
Blog / Improving user security in your web apps with email OTP auth
6 min

Improving user security in your web apps with email OTP auth

Understand how email OTP authentication works and how you can implement it in a SvelteKit application.

Improving user security in your web apps with email OTP auth

To discover a balance between security and user convenience, one growing trend we have seen recently is the implementation of passwordless authentication. Today, both small and large companies are transitioning to using passwordless authentication methods over traditional password-based ones, such as Expensify (whose transition was also covered in a Forbes article in 2023). Appwrite has always maintained support for both types of authentication, featuring phone-based OTPs (one-time passwords) and magic URLs in our list of authentication methods.

With the recent Appwrite 1.5 release, we added a new passwordless authentication method: email OTPs. In this blog, you’ll learn how email OTP authentication works and how you can implement it in a SvelteKit application.

What is email OTP authentication?

Email OTP authentication lets users create accounts using their email address and sign in using a 6-digit code delivered to their email inbox. This method is similar to Magic URL login but can provide a better user experience in some scenarios.

Email OTP vs magic URL

Email OTP authentication sends an email with a 6-digit code that a user needs to enter into the app, while magic URL authentication delivers a clickable button or link to the user's inbox. Both allow passwordless login flows with different advantages.

Benefits of email OTPDownsides of email OTP
Doesn't require the user to be signed into their email inbox on the device
Expires quicker
Doesn't disturb the application flow with a redirect
Requires more inputs from the user
Doesn't require deep linking on mobile apps

Implementing email OTP in a SvelteKit app

Now that we have a basic understanding of email OTP authentication, let’s learn how we can implement it in a SvelteKit application.

Set up Appwrite 1.5

You can use email OTP authentication either by registering on Appwrite Cloud or self-hosting Appwrite on your system or an external VM/VPS.

You can self-host Appwrite using the following Docker command:

Bash
docker run -it --rm \
    --volume /var/run/docker.sock:/var/run/docker.sock \
    --volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
    --entrypoint="install" \
    appwrite/appwrite:1.8.1

Once that is done, set up email delivery on your self-hosted Appwrite instance. This can be done by visiting your appwrite directory and updating the .env file in a similar manner as follows:

_APP_SMTP_HOST=smtp.sendgrid.net
_APP_SMTP_PORT=587
_APP_SMTP_SECURE=tls
_APP_SMTP_USERNAME=YOUR-SMTP-USERNAME
_APP_SMTP_PASSWORD=YOUR-SMTP-PASSWORD
_APP_SYSTEM_EMAIL_ADDRESS=YOUR-SENDER-EMAIL

Don’t forget to run docker compose up -d after you update the environment variables to ensure the changes are implemented in your instance.

Once your Appwrite instance is set up, create a project and copy both the Appwrite API endpoint and project ID.

Create your SvelteKit web app

In order to create your SvelteKit application, open your terminal and enter the command npm create svelte@latest to create a project.

Bash
npm create svelte@latest

create-svelte version 6.0.10

┌  Welcome to SvelteKit!

◇  Where should we create your project?
│  appwrite-email-otp

◇  Which Svelte app template?
│  Skeleton project

◇  Add type checking with TypeScript?
│  No

◇  Select additional options (use arrow keys/space bar)
│  Add ESLint for code linting, Add Prettier for code formatting

└  Your project is ready!

Once that is done, enter the directory and install any existing dependencies.

Bash
cd appwrite-email-otp
npm i

Also, create a file .env in the root directory and add your Appwrite API endpoint and project ID.

PUBLIC_APPWRITE_ENDPOINT=
PUBLIC_APPWRITE_PROJECT_ID=

Install the Appwrite Web SDK and prepare the app logic

Once your project is ready, install the Appwrite Web SDK using NPM:

Bash
npm i appwrite

As soon as that’s done, we can start preparing our application logic. For that, visit the src/lib directory and create a file appwrite.js. Add the following code to the same.

JavaScript
import { PUBLIC_APPWRITE_ENDPOINT, PUBLIC_APPWRITE_PROJECT_ID } from "$env/static/public";
import { Client, Account } from "appwrite";

const client = new Client()
    .setEndpoint(PUBLIC_APPWRITE_ENDPOINT)
    .setProject(PUBLIC_APPWRITE_PROJECT_ID);

export const account = new Account(client);

In the same directory, create a file user.js and add the following code.

JavaScript
import { account } from "./appwrite";
import { ID } from "appwrite";

export const user = {
    createOtp: async (email) => {
        return await account.createEmailToken({
            userId: ID.unique(),
            email,
            phrase: true
        });
    },

    verifyOtp: async (userId, secret) => {
        return await account.createSession({
            userId,
            secret
        });
    }
}

Update the application UI

Once the authentication logic is ready, we can create the web app’s user interface. To prioritize ease of understanding and simplicity, the example will not feature any styling-related code.

In the src/routes directory, visit the file +page.svelte and replace it with the following code.

HTML
<script>
    import { user } from "$lib/user.js";

    var token;
    var securityPhrase = "Create OTP first to get the security phrase.";

    async function createOtp(e) {
        e.preventDefault();
        const formData = new FormData(e.target);
        token = await user.createOtp(formData.get('email'));
        securityPhrase = `Security phrase: ${token.phrase}`;
        alert("OTP sent to email");
    }

    async function verifyOtp(e) {
        e.preventDefault();
        const formData = new FormData(e.target);
        const response = await user.verifyOtp(token.userId, formData.get('otp'));
        alert("OTP verified");
    }
</script>

<section>
    <div class="container">
        <div id="email">
            <h2>Enter Email</h2>
            <form on:submit={createOtp}>
                <input type="email" name="email" id="email" placeholder="team@appwrite.io" required>
                <button class="button" type="submit">Submit</button>
            </form>
        </div>

        <div id="otp">
            <h2>Enter OTP</h2>
            <form on:submit={verifyOtp}>
                <input type="text" name="otp" id="otp" placeholder="012345" required>
                <button class="button" type="submit">Submit</button>
            </form>
            <p>{securityPhrase}</p>
        </div>
    </div>
</section>

This UI features both steps of the email OTP flow, entering the email to send an OTP followed by entering the OTP to login. It also features the security phrase sent on the email along with the OTP so that the user can verify that a third party didn’t initiate the authentication flow.

Once this is done, you can test the application by running the command npm run dev and opening the app URL in your browser.

Build fast, scale faster

Backend infrastructure and web hosting built for developers who ship.

  • Start for free
  • Open source
  • Support for over 13 SDKs
  • Managed cloud solution

Next steps

And with that, our demo application is ready. If you liked this project or want to investigate the full codebase, visit the GitHub repository.

For more information about Appwrite Authentication, visit the following resources:

  • Appwrite Authentication Docs: These docs provide more information on how to use the different methods offered under Appwrite Authentication.
  • Appwrite Discord: Connect with other developers and the Appwrite team for discussion, questions, and collaboration.

Frequently asked questions

  • What is email OTP authentication?

    Email OTP authentication lets users sign in with a 6-digit one-time code sent to their email instead of a password. It is a passwordless flow that pairs well with magic URLs and other modern auth methods, and it is part of Appwrite Auth.

  • How is email OTP different from magic URL login?

    Magic URL sends a clickable link to the inbox, while email OTP sends a numeric code the user enters in the app. OTPs are better when users may not be signed into their email on the same device, when you want to avoid redirect flows, or when deep linking on mobile is awkward. The trade-off is more typing and a shorter expiry window.

  • How do I implement email OTP in a SvelteKit app with Appwrite?

    Use the Appwrite Web SDK in your SvelteKit app, call createEmailToken with a user ID and email to send the code, then call createSession with the user ID and the entered code to log the user in. The full flow uses two screens: one for the email and one for the OTP.

  • Do I need Appwrite Cloud or can I self-host for email OTP?

    Both work. On Appwrite Cloud the feature is ready out of the box. On a self-hosted instance, configure SMTP credentials in your .env file so Appwrite can actually deliver the code, then restart with docker compose up -d.

  • Is email OTP secure enough for production apps?

    Email OTP is more secure than passwords for most users because it removes password reuse and weak passwords from the equation. The trade-off is dependency on email delivery and inbox security. For higher-assurance use cases, pair email OTP with multi-factor authentication using TOTP.

  • What other passwordless options does Appwrite support?

    Appwrite Auth supports email OTP, phone OTP via SMS, magic URLs, OAuth providers, passkeys, and anonymous sessions. You can combine them with MFA factors like TOTP or email codes for a layered auth strategy. See the Appwrite Auth docs for the full list.

Start building with Appwrite today