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 OTP | Downsides 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:
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.6.0
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.
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.
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:
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.
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.
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.
<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="[email protected]" 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.