Title: Authentication, Authorization, and Auth-service, Oh my!

Remitly
Remitly
7 min readSep 8, 2021

--

Author: Jesse Epperson

Overview

In some areas with small populations and high community trust, it is common for people to leave their homes or cars unlocked. One reason for doing so is the belief that the risk of abuse is low and that only those who should gain access, will gain access.

Applying an equivalent level of security to one’s online accounts is unwise as the population is effectively global and the community trust is quite low. To prevent unwanted access to online accounts, both authentication1 and authorization2 are needed to verify that only those who should access an account, can access an account.

A Remitly account allows a customer to send their money to someone else in another country. Without any forms of authentication or authorization on Remitly accounts, a bad actor could use any customer’s account to send money to themselves, thus defrauding the customers we strive to serve.

In this post we will describe the mechanisms used at Remitly to authenticate and authorize customer account activity and discuss why these mechanisms were chosen.

1 Authentication verifies that a user is who they say they are.

2 Authorization states which resources a user can and cannot access.

Problem

The end result of authenticating a customer is confidence that they are who they claim to be. Authentication is almost always based on something you know (e.g., password, answers to security questions), something you have (e.g., phone to receive SMS, hardware security key), something you are (e.g., fingerprint, facial features), or a combination of these three.

Here we focus on the most common type of ‘something you know’ authentication — passwords. Remitly uses additional authentication factors but we’ll leave exploring those factors for future blog posts.

Once a Remitly customer enters their password, we must verify that the password is correct and if so, grant them access to their Remitly account. In order to reduce customer friction, we only want to require a correct password entry once per session. We also explore alternatives for password re-entry which we call, “password proxies”.

To ensure that access to Remitly customer accounts is only granted to their respective owners, we ask ourselves the following questions:

“How can we securely verify password correctness? And once a password is verified as correct, how can we avoid requiring password re-verification on every request?

Critical Thinking

To answer these questions, we first break down some requirements for our solutions.

Verifying Passwords

The simplest method of verifying passwords would involve storing all customer passwords in a database. In this scenario, verifying passwords is as easy as doing a lookup by customer to check whether the given password matches the password stored for that customer.

The primary reason not to adopt this method is the risk of exposing plaintext3 passwords to anyone who can read data from the database, which would allow anyone to impersonate these customers by signing in with their password. To avoid this risk, modern best practices suggest storing a cryptographic hash4 of the plaintext password instead of the password itself.

We decided that our solution for verifying password correctness should meet the following criteria:

  • Passwords should not be stored in plaintext
  • Passwords should be hashed using a cryptographic hash function4
  • Only the hashed passwords should be stored in Remitly’s databases
  • The chosen hash function should be recommended by the larger security community
  • The chosen hash function should have implementation libraries in programming languages used at Remitly

3 Plaintext is data that can be read or used without needing to decrypt it first (e.g. “Jesse” vs “Ahf8UiSUWOvmF0x3UEbQEw==”). Note: this encrypted sample data was base64 encoded for ease of display. Can you determine the simple one-word secret used to encrypt this data?

4 Cryptographic hash functions (CHFs) take plaintext as input and output a hash that cannot easily be reversed to reveal the plaintext. Running the same password through the same CHF will output the same hash, allowing verification of password correctness without needing to store the plaintext password.

Password Proxies

In order to continue being confident that the customer is who they claim to be without requiring repeated password entry and verification, we need a proxy5 for the password. Many modern systems use authentication tokens (sometimes called session tokens) as password proxies, which is why we use the term ‘token’ interchangeably with ‘password proxy’. In such systems, a token is returned to the customer after successful password verification, though most customers never notice them as they function behind the scenes within the browser or mobile app.

When a customer takes their next action after logging in (e.g., viewing the status of their most recent transfer), this token is passed to Remitly’s internal systems and used to verify that the customer is who they claim to be and that they are authorized6 to be taking that action.

We decided that our solution for avoiding password re-verification on every request should meet the following criteria:

  • The customer should not need to directly interact with the token
  • The token should be tied to one and only one customer session
  • The token should be revocable (e.g., logging out of Remitly should invalidate)
  • Having access to a revoked token should not grant access to the account it is associated with

One key distinction between token solutions is whether they are stateful or stateless. Stateful solutions require storing some or all of the token in a database, which must be queried upon token validation to ensure the token is still valid. Stateless solutions contain enough information in the token itself to verify validity, typically using some form of digital signature to ensure the data can be trusted, and thus do not require calling into a database to validate.

5 In this context the term “proxy” refers to an entity that is authorized to act on behalf of another — as in a token that is authorized to function as a validated password.

6 Currently post-login authorization at Remitly primarily enforces that customer X can only access customer X’s data. I.e. any given customer is not allowed to view other customers’ account information or take actions on their behalf.

Solution

We created an authentication application to serve as Remitly’s solution for verifying password correctness and managing authentication tokens. This microservice7 was created to be the primary source of truth for credentials (email and password) and authentication tokens for Remitly and Passbook. Because auth-service was created in 2019 we had the chance to update how we verify passwords and what type of tokens we use as a password proxy.

7 Importantly, being a microservice means its code is decentralized (not part of a monolithic code base), it has a clear interface for other services that wish to call into it, and it strives to be a 12-factor app.

Verifying Passwords

When deciding how to verify passwords, the question primarily comes down to which cryptographic hash function to use. We decided to use the ID variant of Argon2 as the cryptographic hash function because it is recommended by experts and meets our requirements. Remitly’s authentication application is written in a language that uses a pre-built implementation of Argon2. We use a convenience wrapper around this package to further isolate ourselves from making cryptography-related mistakes.

Password Proxies

When deciding which type of password proxy to use, we first needed to decide between stateless and stateful authentication tokens. We decided to use stateful tokens because of their ability to be instantly revoked and because their implementation is less complex. JSON Web Tokens (JWTs) were one of the stateless token contenders because of their widespread adoption, but were ultimately decided against them because of their inability to reasonably support revocation8. When a customer logs out or resets their password, we want to immediately invalidate the associated active tokens for that customer to reduce the risk of those tokens being misused by bad actors. At Remitly, we opted to generate stateful authentication tokens and store them along with other session metadata, and return the token to the customer to be used as a password proxy for the remainder of the session.

8 Although you can build denylist support for JWTs, this renders the solution stateful, which nullifies the primary advantage we saw in using JWTs: statelessness. Using denylists for JWTs does not provide many additional benefits as compared with our chosen solution, but does increase risk of misconfigurations (example, example2). More reading about JWT revocation here and here.

Summary

A central part of authenticating Remitly customers involves verifying the correctness of the passwords they input when logging in. To do this, auth-service hashes passwords using the Argon2 hashing algorithm and compares the resultant hash with the stored hash. If they match, then the given password was correct and we grant access to their account. To avoid requiring repeated password verification, we return a stateful authentication token upon successful login, which is sent with subsequent requests to verify that the customer is, in fact, who they claim to be.

--

--