How to Build Passwordless Auth With Twilio Verify in Rust
Time to read:
How to Build Passwordless Auth With Twilio Verify in Rust
Typing or generating a unique password for every new account or website is a hassle. Although password managers help, passwordless authentication (auth) offers a more streamlined and secure alternative.
In this tutorial, you will learn about passwordless auth, and build a Rust app that uses Twilio Verify to implement it.
Prerequisites
Before you begin, ensure you have the following:
- A free Twilio account. Click here to create a free account if you are new to Twilio.
- A phone that can receive SMS
- Rust and Cargo
- Your favourite text editor or IDE (such as neovim or Visual Studio Code)
- Your favourite web browser
Architecture
This application will be a small API with four routes:
- A GET and a POST route with the path "/". These display the sign-in form where users can enter their mobile phone number to receive the OTP code, and validate submissions of that form. If the form successfully validates, then an OTP code will be sent to the phone number provided.
- A GET and a POST route with the path "/verify". These display a form to validate the OTP code that the user received, and validate submissions of that form. After submission of the form, the user will see if the code was valid or not.
What is passwordless auth?
Passwordless auth is an authentication method where the user does not need a password in order to log into an app or system. Rather, the user's mobile device receives a one-time code. In this authentication method, users are authenticated using other unique and more secure alternatives like one-time passwords (OTP), SMS, Passkeys, Silent Network Authentication (SNA), Voice, or email notification.
Here's a breakdown of how it works, using SMS:
- User initiation: When a user attempts to log in, instead of entering a password, they provide their phone number as their unique identifier and are redirected to a form where they can validate the OTP code that they will receive.
- OTP generation and delivery: The server initiates an OTP generation request with Twilio Verify. Twilio Verify then dispatches a unique, temporary code via SMS to the user.
- User verification: The user receives the OTP on their mobile device and input this code into the validation form.
- Code validation: Using Twilio Verify, the server verifies the entered OTP against the one that was sent, earlier. If the codes match, the user is successfully authenticated and granted access.
This approach significantly enhances security by leveraging the inherent security features of the user's mobile device as a second factor in the authentication process. It also eliminates the need for them to memorize passwords, thereby simplifying the authentication process and enhancing user convenience.
Passwordless authentication has additional advantages, including the following:
- Enhanced security: Reduces the risk of password-related breaches.
- Convenience: Eliminates the need for users to remember and manage multiple passwords.
- Reduced friction: Streamlines the login process, improving the user experience.
- Scalability: Easily scalable with Twilio's infrastructure.
Build the app
Step 1: Set up the project
Set up a new Rust project using by running the following commands, where you store your Rust projects:
If you're using Microsoft Windows, replace the third line in the command above with the following:
Step 2: Install the required dependencies
Next, install the required dependencies, by running the following:
In case you're not familiar with the crates, here's a short description of the dependencies that you just installed:
Now, open the project directory in your preferred text editor or IDE.
Step 3: Set the required environment variables
Dotenv files (commonly named .env) are used to store the configuration information that your app needs during development, separate from the application's code. For this tutorial, it will be your Twilio credentials (i.e., your Twilio Account SID and Auth Token) and a Verify Service SID.
In your project's top-level directory, create a file named .env. Then, in .env add the three variables below.
With placeholders for the environment variables set in .env, you next need to retrieve the credentials to set as their values. To do that, sign into the Twilio Console. There, click the black and white up arrow at the bottom of the page, and you should see your Account SID and Auth Token in the Workbench; as shown in the image below.
Copy these values and paste them into .env as the values for TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN.
Next, navigate to Products and Services > Verify > Services. On this page, click Create new. Then, fill out the initial form with the configuration values shown in the screenshot below, and click Continue.
In the next step, leave Enable Fraud Guard set to "Yes" and click Continue to finish creating the service.
After creating the service, copy the Service SID and paste it into .env as the value of TWILIO_VERIFY_SERVICE_SID.
Step 4: Build the base Rust application
The next thing to do is start fleshing out the base application. To do that, replace the existing code in src/main.rs with the code below:
The updated code, refactors the main() function to load environment variables from the variables defined in .env using dotenvy, and loads three variables in the application's configuration (app_config): TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, and TWILIO_VERIFY_SERVICE_SID. These are your Twilio Account SID and Auth Token, and a Verify Service SID. You'll retrieve these shortly.
Then, it adds session and template support, backed by the Handlebars templating language to the application, sets up the application's routing table with a single route, adds session support, then starts the application listening on port 8080.
Step 5: Add the ability to send an OTP
Now, you'll add the first of two features: the ability to send an OTP code when requested by the user. To do that, add the following to src/main.rs.
Then, after the initialisation of hbs in the main() function, add the following:
The show_signin_form() function will be called in response to GET requests to the application's default route "/", rendering templates/forms/signin.hbs with no template data (NoData).
Now, in the templates/forms directory, create a new file named signin.hbs, and in that file paste the code below:
As you can see from the HTML above, it is a simplistic HTML page with a form with a single field named "phone". Note that the field's type and inputmode attributes are set to "tel". This hints to browsers on mobile devices to render a virtual keyboard most appropriate for entering telephone data.
Then, back in src/main.rs, add the following code at the end of the file.
The send_otp_code() function processes requests to the sign-in form. It retrieves the phone number parameter from the request and, if it's available, stores it in the current session. If it's not available, it redirects the user back to the sign in form.
Then, it attempts to send an OTP code to the user's phone number via SMS using Twilio Verify. If the code was successfully sent, the user is redirected to the "/verify" route, which you'll build next.
Now, in the main() function in src/main.rs, update the initialisation of app to match the following:
The code adds the POST variant of the default route ("/") to the application's routing table, having requests to it handled by send_otp_code().
Step 6: Add the ability to verify an OTP
You'll now add the functionality to verify the OTP codes the user receives. Start by adding the code below to the end of src/main.rs.
Then, update the initialisation of hbs in main() to match the following:
The show_verify_otp_form() method renders templates/forms/verify.hbs, providing the form for the user to enter to validate their OTP code.
The verify_otp_code() function retrieves the phone number from the session. If it's not present or empty, the user is redirected to the sign in form. Otherwise, it attempts to parse the request data and retrieve the "code" parameter from the POST request.
If the code parameter wasn't present or was empty, the user is redirected to the verify OTP code form. If the parameter was present, it uses the Twilio Rest Client to validate that the code supplied was the same as the one that was sent to the user's phone number.
If the code was valid:
- The user's phone number is deleted from the current session
- templates/forms/verification-status.hbs is rendered as the function's response, along with two template variables:
statusset totrueandmessageset to "Verification was successful".
If the code was invalid, templates/forms/verification-status.hbs will be rendered with status set to false and message set to "Verification failed".
With that done, in the templates/forms directory create a new file named verify.hbs and paste the code below into the file.
The HTML renders another, simplistic, form with a single field for the received OTP code. The field uses the pattern and maxlength field attributes to ensure that the only valid input is a 6-digit code. It also sets the inputmode attribute to "numeric" to have the browser display a keyboard appropriate for entering digits, making it easier for the user to only enter numeric input.
Now, create another file in the templates/forms directory named verification-status.hbs and paste the code below into the file.
The HTML renders the message in the Message template variable, and styles it as either a success or error, based on the value of the Success template variable.
With the templates created, the final thing to do is to finalise the definitions in the application's routing table. Do that by updating the initialisation of app in src/main.rs to the following:
The revised initialisation adds the GET and POST forms of the "/verify" route, handled by the show_verify_otp_form() and verify_otp_code() functions, respectively. It also adds two static route definitions:
- "/assets", which serves files from the /assets directory
- "/favicon.ico" to serve the application's favicon. This isn't strictly necessary, but numerous services and clients still request it and it helps improve brand recognition and trust, so it's handy to have.
Step 7: Download the application's CSS file
The last step in the process is to download the application's CSS file from the project's GitHub repository, to the project's assets/css directory, naming it styles.css. Additionally, download the application's favicon.ico file to the assets directory.
Test that the application works
Finally, it's time to test that the code works as expected. Start the application by running the following command.
Your server will start on port 8080, as shown by terminal output similar to the following.
You can now navigate to http://localhost:8080 to test your passwordless auth flow.
Enter your phone number and click Send OTP.
You will be redirected to the Verify OTP Code form. After you receive the code via SMS enter it into the form and submit the form. Whether the code was or not, you'll see a confirmation printed in the browser.
That's the essentials of implementing passwordless authentication in Rust using Twilio Verify
Passwordless authentication using Rust and Twilio Verify presents a compelling alternative to traditional password-based systems. It offers a blend of enhanced security and user convenience, making it an attractive option for modern applications. While passwordless auth introduces some extra complexity to your application, the benefits — especially regarding security and user experience — are worth the investment.
Matthew Setter is a PHP, Go, and Rust Editor in the Twilio Voices team. He’s also the author of Mezzio Essentials and Deploy with Docker Compose. You can find him at msetter@twilio.com. He's also on LinkedIn and GitHub.
Related Posts
Related Resources
Twilio Docs
From APIs to SDKs to sample apps
API reference documentation, SDKs, helper libraries, quickstarts, and tutorials for your language and platform.
Resource Center
The latest ebooks, industry reports, and webinars
Learn from customer engagement experts to improve your own communication.
Ahoy
Twilio's developer community hub
Best practices, code samples, and inspiration to build communications and digital engagement experiences.