Back to blog

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.

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 deviceExpires quicker
Doesn't disturb the application flow with a redirectRequires 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

Email OTP authentication is not yet released on Cloud, which is on Appwrite 1.4.x. This is a feature available in Appwrite 1.5.x and will be available on Appwrite Cloud later. In the meantime, you can use it by 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.5.5

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(ID.unique(), email, 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.

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.

Subscribe to our newsletter

Sign up to our company blog and get the latest insights from Appwrite. Learn more about engineering, product design, building community, and tips & tricks for using Appwrite.