SMS User Authentication With Vapor and AWS
In this SMS user authentication tutorial, you’ll learn how to use Vapor and AWS SNS to authenticate your users with their phone numbers. By Natan Rolnik.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
SMS User Authentication With Vapor and AWS
25 mins
- Getting Started
- How SMS Auth Works Behind the Curtain
- Interacting With AWS SNS
- Your First API: Sending the SMS
- Your Second API: Authenticating the Received Code
- Validating the Code
- Returning the User and the Session Token
- Testing the APIs With cURL
- Registering the Routes
- Calling the First API
- Calling the Second API
- Where to Go From Here?
Returning the User and the Session Token
Right below the code you added in UserController.swift, add this:
private func verificationResponseForValidUser(
with phoneNumber: String,
on req: Request) -> EventLoopFuture< UserVerificationResponse> {
// 1
return User.query(on: req.db)
.filter(\.$phoneNumber == phoneNumber)
.first()
// 2
.flatMap { queriedUser -> EventLoopFuture<User> in
if let existingUser = queriedUser {
return req.eventLoop.future(existingUser)
}
return User(phoneNumber: phoneNumber).save(on: req)
}
.flatMap { user -> EventLoopFuture<UserVerificationResponse> in
// 3
return try! Token.generate(for: user)
.save(on: req)
.map {
UserVerificationResponse(
status: "ok",
user: user,
sessionToken: $0.value)
}
}
}
}
There’s a lot going on here, but it’ll all make sense if you look at everything piece by piece:
- First, look for an existing user with the given phone number.
-
queriedUser
is optional because the user might not exist yet. If an existing user is found, it’s immediately returned inEventLoopFuture
. If not, create and save a new one. - Finally, create a new
Token
for this user and save it. Upon completion, map it to the response with the user and the session token.
Build and run your server. It should compile without any issues. Now it’s time to call your APIs!
Testing the APIs With cURL
In the following example, you’ll use curl
in the command line, but feel free to use another GUI app you might feel comfortable with, such as Postman or Paw.
Now open Terminal and execute the following command, replacing +1234567890
with your phone number. Don’t forget your country code:
curl -X "POST" "http://localhost:8080/users/send-verification-sms" \
-H 'Content-Type: application/json; charset=utf-8' \
-d $'{ "phoneNumber": "+1234567890" }'
Oops. This request returns an HTTP 404 error: {"error":true,"reason":"Not Found"}
.
Registering the Routes
When you see a 404 error, it’s most likely because the functions weren’t registered with the Router
in use, or the HTTP method used doesn’t match the registered method. You need to make UserController
conform to RouteCollection
so you can register it in the routes configuration. Open UserController.swift and add the following at its end:
extension UserController: RouteCollection {
func boot(routes: RoutesBuilder) throws {
// 1
let usersRoute = routes.grouped("users")
// 2
usersRoute.post("send-verification-sms", use: beginSMSVerification)
usersRoute.post("verify-sms-code", use: validateVerificationCode)
}
}
The code above has two short steps:
- First, it groups the routes under the
users
path. This means that all routes added tousersRoute
will be prefixed byusers
— for example,https://your-server.com/users/send-verification-sms
. - Then it registers two HTTP POST endpoints, providing each endpoint with one of the handler methods you defined above.
Now, open routes.swift and add this line inside the only existing function. This function registers your app’s routes:
try app.register(collection: UserController())
Calling the First API
Build and run your project again and try the previously failing curl
command by pressing the up arrow key followed by Enter. You’ll get the following response with a new UUID:
{
"attemptId": "477687D3-CA79-4071-922C-4E610C55F179",
"phoneNumber": "+1234567890"
}
This response is your server saying that sending the SMS succeeded. Check for the message on your phone.
Excellent! Notice how the sender ID you used in the initialization of AWSSNSSender
is working correctly.
Calling the Second API
Now you’re ready to test the second part of the authentication: verifying the code. Take the attemptId
from the previous request, the phone number you used in the previous step, and the code you received and place them into the following command. Then run the command in Terminal:
curl -X "POST" "http://localhost:8080/users/verify-sms-code" \
-H 'Content-Type: application/json; charset=utf-8' \
-d $'{"phoneNumber": "+1234567890", "attemptId": "<YOUR_ATTEMPT_ID>", "code": "123456" }'
If you replaced each parameter correctly, the request will return an object with three properties: the status, the user object and a session token:
{
"status": "ok",
"user": {
"id": "31D39FAD-A0A9-46E7-91CF-AEA774EA0BBE",
"phoneNumber": "+1234567890"
},
"sessionToken": "lqa99MN31o8k43dB5JATVQ=="
}
Mission accomplished! How cool is it to build this yourself, without giving up on your users’ privacy or adding big SDKs to your client apps?
Where to Go From Here?
Download the completed project files by clicking the Download Materials> button at the top or bottom of this tutorial.
Save the session token in your client apps as long as the user is logged in. Check out Section III: Validation, Users & Authentication of the Server-Side Swift with Vapor book to learn how to use the session token to authenticate other requests. The chapters on API authentication are particularly helpful.
You can also read the documentation of Vapor’s Authentication API to better understand where you should add the session token in subsequent requests.
Do you want to continue improving your SMS authentication flow? Try one of these challenges:
- Start using a PostgreSQL or MySQL database instead of in-memory SQLite and make changes to your app accordingly.
- To avoid privacy issues and security breaches, hash phone numbers before saving and querying them, both in the
User
and theSMSVerificationAttempt
models. - Think of ways to improve the flow. For example, you could add a
isValid
Boolean to make sure the code is only used once, or delete the attempt upon successful verification. - Implement a job that deletes expired and successful attempts.
We hope you enjoyed this tutorial. If you have any questions or comments, please join the forum discussion below!