Appwrite, as a backend-as-a-service, provides phone authentication where users can create accounts and log in using SMS. Appwrite requires an SMS provider to be set up for this to work. One such SMS provider is Vonage.
In this tutorial, we will learn how to set up a Vonage account, configure it to be used with Appwrite, and build a Next.js app to put it all together.
GitHub
Check out the source code here.
Prerequisites
To follow along in this tutorial, we require the following:
New to Appwrite?
Appwrite is an open-source backend-as-a-service that abstracts all the complexity of building applications by providing a set of APIs for core backend needs. Appwrite does a lot of heavy lifting for developers by providing authentication, database, file storage, functions, webhooks, and much more.
What is Vonage?
Vonage provides APIs that help organizations enhance customer experience by scaling to best meet their needs. They provide several communication channels like Voice, Video, SMS, MMS, and social chat apps like Facebook, Whatsapp, and Viber, which businesses can use to reach and engage customers.
In this tutorial, we will use the SMS API integrated with Appwrite for phone authentication.
Setting up Vonage
We’ll first need to create a Vonage account. To create an account, head over to Vonage’s website to signup to use the Communication APIs. The signup page looks like this:
After signing up, the Vonage API dashboard is displayed.
We can send SMS, make voice calls and perform other functions from the dashboard. We can also see the API key and API Secret on the dashboard. We will keep them handy as we need them in the next step.
Installing Appwrite
We need to have an Appwrite server running locally on our system. We can set it up locally or using any of the one-click setups. To set it up locally, run the command below:
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.3.8
The command installs Appwrite on our local machine. We will use the default configurations provided. An appwrite
folder is created that contains a docker-compose.yml
and .env
file.
Once the installation is complete, we will update two environment variables in the .env
file. The environment variables are _APP_SMS_PROVIDER
and _APP_SMS_FROM
.
_APP_SMS_PROVIDER
is a connection string that helps Appwrite connect to the Vonage API, and _APP_SMS_FROM
is our Vonage phone number or brand name.
_APP_SMS_PROVIDER=phone://[API KEY]:[API SECRET]@vonage
_APP_SMS_FROM=<YOUR PHONE NUMBER OR BRAND NAME>
After updating the environment variables, we will restart our Appwrite server for the changes to take effect. To restart the server, we will run the command below from the appwrite
folder that was created during installation.
docker compose up -d
Then, we will navigate to http://localhost
to sign up/sign in to our Appwrite account.
Next, we will create a new project. We will name our project Appwrite-Vonage
After the creation of the project, we will copy the Project ID
and API Endpoint
from the Settings tab, as we will need them in the next step.
Setting up our Next.js project
We need to create a Next.js project to see the phone authentication in action. Open the terminal and enter the following command to scaffold a Next.js project.
npx create-next-app phone-authentication
We will have a series of prompts
Next, we will go to the project directory and start the development server on localhost:3000
with the commands below.
cd phone-authentication && npm run dev
To use Appwrite in our application, we will install the Appwrite client-side SDK for web applications using the command:
npm install appwrite
Next, we will create a .env.local
file in our project root for our environment variables. We will add the following entries to our .env.local
file.
PROJECT_ID=<APPWRITE_PROJECT_ID>
ENDPOINT=<APPWRITE_ENDPOINT>
The values of the environment variables were obtained in the previous step when we created our Appwrite project. To access the environment variables within our application, we will modify the next.config.js
file. The file should look like this.
/** @type {import('next').NextConfig} */
const nextConfig = {
env: {
PROJECT_ID: process.env.PROJECT_ID,
ENDPOINT: process.env.ENDPOINT
}
}
module.exports = nextConfig
Setting up the Appwrite SDK
We will create our Appwrite service that will help us abstract all the SDK calls. Create a file src/appwrite.js
and fill it in with the following content.
import { Client, Account } from 'appwrite';
const client = new Client();
client
.setEndpoint(process.env.ENDPOINT)
.setProject(process.env.PROJECT_ID);
const account = new Account(client);
export const getAccount = async () => account.get();
export const createPhoneSession = async (number) => account.createPhoneSession('unique()', number);
export const updatePhoneSession = async (userId, secret) => account.updatePhoneSession(userId, secret);
export const deleteSession = async () => account.deleteSession('current');
The code above imports the Client and Account objects from Appwrite. Then, an instance of the Client object is created. We then set our Endpoint
and Project ID
that we obtained from the previous step into our client instance. Next, an instance of the Account object is also created.
We have created four different functions that return methods of the account
instance. The methods are:
-
getAccount
: gets the current logged-in user account. -
createPhoneSession
: creates a new phone session by sending an SMS with a secret key for creating a session. This is where we will see our Vonage SMS API action. -
updatePhoneSession
: completes the creation of the phone session by using the code sent via SMS and the userId gotten from thecreatePhoneSession
function. -
deleteSession
: deletes a current session by logging out the user.
We will see these functions in action when we start building our UI in the next section.
Building our user interface
Our UI will consist of two pages, one for the home page and the other for the login.
First, we will modify the file src/app/page.js
,
which is our home page. The file should look like this
'use client';
import { getAccount, deleteSession } from '@/appwrite';
import React, {useEffect} from 'react';
import { useRouter } from 'next/navigation';
export default function Home() {
const router = useRouter();
useEffect(() => {
(async() => {
try{
await getAccount();
}
catch(e){
console.log(e);
router.push('/login');
}
})()
}, []);
const logout = async () => {
try {
await deleteSession();
router.push('/login');
} catch (e) {
console.log(e);
}
};
return (
<div style={{textAlign: "center"}}>
<h1>Welcome</h1>
<button onClick={logout}>Logout</button>
</div>
)
}
The code above tries to fetch the user account details from Appwrite using the getAccount
function in our src/appwrite.js
file. If the user is not found, an exception is thrown and then caught in the catch block, and the user is redirected to the login page. There is also a logout button that deletes the current session and redirects us to the login page.
Next, we will create a file src/components/SignupForm.js
, our signup form component. The content of the file should look like this.
'use client';
import { createPhoneSession } from "@/appwrite";
import React, { useState } from "react";
import VerificationForm from "./VerificationForm";
const SignupForm = () => {
const [phoneNumber, setPhoneNumber] = useState("");
const [isVerificationSent, setIsVerificationSent] = useState(false);
const [userId, setUserId] = useState("");
const handlePhoneNumberChange = (event) => {
setPhoneNumber(event.target.value);
}
const handleSubmit = (event) => {
try {
const response = createPhoneSession(phoneNumber);
response.then(user => {
setUserId(user['userId'])
})
event.preventDefault();
alert("verification code has been sent");
setTimeout(() => {
setIsVerificationSent(true);
}, "1000");
}
catch (e) {
console.log("error while creating phone session", e);
}
}
return (
<>
{!isVerificationSent && (
<form onSubmit={handleSubmit}>
<div>
<label style={{ marginRight: "10px" }}>
Phone Number:
</label>
<input type="text" name="phoneNumber" value={phoneNumber} onChange={handlePhoneNumberChange} style={{ width: "150px" }} required />
</div>
<div style={{ textAlign: "center", marginTop: "20px" }}>
<input type="submit" value="Get Code" style={{ width: "100px" }} />
</div>
</form>
)}
{isVerificationSent && (
<VerificationForm userId={userId}/>
)}
</>
)
}
export default SignupForm;
The code above does the following:
- Creates a form for inputting the phone number for verification.
- Then we use the React hook
useState
to manage the state of the phone number and whether a verification code has been sent. - When
Get Code
button is clicked after inputting the phone number, we create a phone session using thecreatePhoneSession
function and send a verification code to the phone number. If the verification code is sent successfully, theVerificationForm
component is displayed. - Then, the
userId
property is passed from the response of thecreatePhoneSession
function as a props to theVerificationForm
component.
Next, we will create a new component src/component/verificationForm.js
, which is where we will input the verification code sent via SMS to our phone number. The file should look like this:
'use client';
import { updatePhoneSession } from "@/appwrite";
import React, { useState } from "react";
import { useRouter } from 'next/navigation';
const VerificationForm = ({userId}) => {
const [verificationCode, setVerificationCode] = useState("");
const router = useRouter();
const handleVerificationCodeChange = (event) => {
setVerificationCode(event.target.value);
};
const handleSubmit = (event) => {
try{
updatePhoneSession(userId, verificationCode)
router.push('/');
event.preventDefault();
}
catch(e){
console.log("error while updating phone session", e);
}
}
return (
<form onSubmit={handleSubmit}>
<div>
<label style={{ marginRight: "10px" }}>
Verification Code:
</label>
<input type="text" name="verificationCode" value={verificationCode} onChange={handleVerificationCodeChange} style={{ width: "150px" }} required />
</div>
<div style={{ textAlign: "center", marginTop: "20px" }}>
<input type="submit" value="Verify" style={{ width: "100px" }} />
</div>
</form>
)
};
export default VerificationForm;
The code above creates a form where we will input our verification code. When the verification code is entered into the form, and the form is submitted, it calls the updatePhoneSession
function that completes the sign in process. When we successfully sign in, we are redirected to the home page.
The video below shows a demo of the whole process. The phone number has been masked in the video.
We can also confirm from our Appwrite instance that the user was successfully logged in.
Conclusion
In this tutorial, we learned how to set up phone authentication using Appwrite and Vonage. We introduced Vonage and the services they provide. We also looked at how to set up a Vonage account. We learned how to install an Appwrite server locally and change the environment variables needed for phone authentication. We built a simple Next.js application to tie everything together.