In an earlier post, we learned the basic concepts of identity, authentication, and authorization — important terms that are easily confused by hackers and technologists.
In this post, we will dive deeper and demystify how apps actually implement authentication. Do it right, and you barely notice it. But do it wrong, and you lock users out or open major security holes.
We'll see how apps authenticate you, from old-school passwords to slick new standards like WebAuthn, and we’ll get our hands dirty discussing security and usability trade-offs, encryption, individual sovereignty, and more!
By the end, you'll have a solid grasp of how real-world apps tackle authentication using the methods we've covered.
Let's get started!
The first thing you do when you log into an app is identify yourself.
This might be using some kind of identifier — typically an email address or phone number — or logging in through Google, Facebook, or some other third-party service, and using their knowledge of your identity instead.
Taking that identification and ensuring that it is yours is the process of authentication [1]. Once you’re authenticated, you can use the app!
In the earlier post, we explained that authentication can be achieved in one of three different ways. You can demonstrate that you:
Only the person you're identifying as would know or own this information, or have that identity.
All modern apps use a technique that boils down to one of these three methods to authenticate you.
So, let’s look at each in turn!
Password-based authentication uses the first of these authentication methods — it verifies that you know something only the user you're logging in as would know: their password, pin, passphrase, or any other secret shared between the user and the service.
To prevent attackers from being able to access your account in case an app’s authentication data is leaked through a hack, the app doesn’t actually store the password, but a hashed [2]
version of it.
Roughly speaking, the hash of your password is like an encrypted version of your password, except that given your password’s hash, it’s very hard (near-impossible, if hashed correctly) to retrieve the original password [3].
Implementing password-based authentication yourself is fraught with difficulty. Some of the “fun” challenges you can anticipate are:
Properly implementing passwords — You need to manage salting, hashing, and encryption, and then store them securely in your database.
Password recovery flows — You’ll also need to implement email-based password recovery. But what if your password-recovery emails go to spam? Having angry users contact customer support because they can't log in is not fun.
Handle "password1@3" — You'll quickly realize that people like to use weak and easy-to-guess passwords.
This could allow hackers to find a password through a dictionary attack [4] — something they can do if they get access to a leaked version of the database, for example — before later gaining access to the running system.
Block bots — Users also really like to reuse passwords [5], so you might have hackers attacking your system because of a data breach on a completely different website. Blocking these hackers when they’re attacking your site from many different IP addresses is a major challenge [6].
Additional friction - There will be quite a few users who won't have the patience to go through the password-recovery flows after forgetting their password. Instead, they just give up and go do something else.
Forcing your user to remember something to log-in can add additional friction and increase churn.
And much more [7]
Each of these steps is hard and requires scarce engineering resources, which is why authentication providers such as Supabase, Auth0, and Clerk offer managed versions, at a cost.
But most significantly, users may forget their password and never even use your site in the first place!
To summarize: passwords are a poor authentication mechanism. They’re hard to implement, unsafe, and lead to a poor user experience!
So, is there a way to authenticate users without relying on something that only they would know?
I wouldn’t still be writing if there wasn’t, so let’s get into that!
Ownership-based authentication is our second authentication method — it involves verifying that you own something that only the user you are attempting to log in as would own, such as their email inbox or phone number.
For instance, when you log in, an app might send you a one-time password (OTP [8]) via email.
This is typically a random short, server-generated, alphanumeric code like ZXY1
, but it can also be a magic link, which is a URL containing the OTP as part of it.
The OTP is a temporary secret that is only shared between the app and the user's email inbox, is valid for that specific interaction, and is invalidated once used.
If you log in using this OTP, then you’ve successfully demonstrated ownership of the email address connected to the user account, and so you must be that user — you’re successfully authenticated!
SMS-based logins can also be used, but SMS is not encrypted and can easily be discovered by anyone on the network by sniffing for text messages [9].
SMS-based logins can also be gamed for profit — a big reason why many apps no longer solely rely on SMS for authentication, or even only offer SMS-based authentication for premium or paying users.
But worst of all, SMS and emails may take minutes to be delivered, or may never be delivered at all [10]!
Is there a way for the user and service to coordinate on a shared, time-based secret, without sending it over an insecure, slow, and unreliable communication channel?
I haven’t stopped writing, so yes!
Authentication apps, such as Google Authenticator, provide time-based OTPs (TOTP [11]).
These authentication apps are set up well in advance of logging in, typically during the first onboarding session.
Instead of using SMS or email to send the OTP to the user, an app on the user’s device generates a TOTP using the current time and a secret shared between the user and the web app.
This is much safer than SMS or email OTP delivery, as it does not rely on the security of external communication channels. The TOTP is generated locally on a device the user possesses and controls, making phishing and interception attacks much more difficult.
Finally, hardware security keys, such as a YubiKey [12], are a physical authentication device that a user can carry around with them. These are often used in high-security environments, such as in an enterprise, and can also create OTPs.
These are really powerful devices, and we’ll dig into them more soon.
Whilst ownership-based authentication methods are often more secure, easier to use (no need to remember a password!), and easier to implement than passwords, they still require the user to do something cumbersome to authenticate.
This is an opportunity to switch devices, get distracted, churn, and never use your app at all!
For this reason, authentication methods that require the user to switch devices are used sparingly, only for sensitive operations such as when logging in on a new device for the first time.
So — can we authenticate a user without requiring them to use two devices? Absolutely!
WebAuthn [13] is an awesome, new(ish) protocol that allows users to authenticate themselves whilst browsing the web.
Instead of using emails, passwords, YubiKeys, or OTPs, the information needed to authenticate a user is stored in an authenticator — typically the browser or device.
The method used for authentication is public key cryptography. It’s the same technology used in Transport Layer Security (TLS) — a networking protocol that is used for nearly all traffic on the internet. It’s fundamental to security, so let’s take a quick aside to dig in:
The idea behind public-key cryptography, or asymmetric encryption, is pretty simple: each person has two keys — a public and private key. The public key is shared with everyone, and the private key is kept to themselves.
Seems pretty simple so far. But we can now do two magical things:
Anyone can send the user a message by encrypting it with the user’s public key. Only the owner of the private key — the user — is able to decrypt that message.
The user can use their private key to sign a message. Anyone with the public key can verify that that user signed the message, and no one other than the user is able to sign it as that user.
The browser or device, acting as an authenticator, generates a new public/private key pair as the user onboards onto a web service. The private key is kept hidden, potentially stored securely in a hardware security module (HSM [14]) on the device, and the public key is shared with the web service.
When the user later wishes to log in, the website asks the browser to sign a specific message. The browser or device does so using the private key, the website verifies it is indeed the same user using the previously shared public key, and the user is now authenticated!
https://webauthn.io has an awesome demo of this — try it out!
With WebAuthn, the user doesn’t need to type their username or remember a password and can log in with a single click — very impressive!
And excitingly, the prompt to log in with WebAuthn will only appear on the correct site, thereby preventing the phishing attacks that were possible with passwords 🎉.
But logging in with a single click also makes it easy for friends, enemies, and attackers to log in with a single click if they get close enough to your machine.
So, can we ensure it’s you actually doing the clicking? Surely!
The HSM can actually prompt the browser for something before it allows access to cryptographic operations, such as generating a public and private key or signing a message. And the browser can prompt the user for this data in turn.
The prompt can ask for a password — another example of knowledge-based authentication — or, even better, ask for biometrics, such as your fingerprint or face.
Laptops and mobile phones commonly support fingerprint readers, so this is a fast, easy, and secure [15] way for users to authenticate.
Clicking a button to sign in and using your fingerprint is multi-factor authentication since it requires two forms of authentication for you to log in: ownership of the device with the HSM and your inherent identity and ownership of your fingerprint.
And because you attest to your own identity, this is a form of self-sovereign identity — we’ll talk about this more in the next post.
WebAuthn does pose a problem, though.
Users typically use a website on multiple devices: their phone, tablet, and laptop. It’s easy to share a password between devices — does WebAuthn offer something similar?
Yes! Well, sort of.
You can delegate the authentication to a roaming device — which could be your phone, laptop, or YubiKey — anything that is accessible via Bluetooth, NFC, or USB. In turn, that device can sign the message on behalf of the main device.
This is an additional layer of protection since malware on a site would need to infect an entirely different device than the main one you’re using to steal your private key — neat!
However, this means that we’d always be reliant on that first device for logging into that service, or we would need to set up a private key on each device we want to log in from — hardly a good user experience, and potentially worse than multi-factor login using passwords and OTPs.
WebAuthn isn’t a panacea, and does come with some notable downsides:
Single point of failure — If you lose a device, you’ll lose access to all of your logins that that device stores the private keys for. Recreating those logins would take ages, and would be a massive pain!
Passkeys [16] are an exciting new feature to help plug this gap and share private keys across devices, allowing for ease of access and easy recovery. The fun part is that Google [17] and Apple’s iCloud would sync these private keys for you — it’s built into the platform!
As excited as I am about Passkeys, they are new and controversial [18, 19], and it’s unclear if they’ll achieve mass adoption.
Inflexibility — Passwords are flexible enough to support most flows that users need [20]. In contrast, WebAuthn only supports a subset of flows.
For example, sharing access to a single account (e.g., to access the bills of an energy provider) with a friend is straightforward with passwords but much tricker with WebAuthn.
Uncertain adoption — Perhaps most important is that users aren’t familiar with the passwordless world.
Will they onboard? Will they log in again on a new device? Will they understand the user flow?
We don’t know yet — and it’s a risk to try out something as innovative as this.
Whilst I really want a passwordless future and think WebAuthn and Passkeys could be our savior, there’s one more form of authentication that is widely used and worth learning about.
The third way we can authenticate is to use something we are — our identity. Biometric authentication is one example of this, and we briefly covered it in the last section.
But we can also implement this by outsourcing identity verification to a third party that users already have an account with — an identity provider [22].
When you see “Login with Google” or “Login with Facebook”, that's identity-based authentication in action.
The identity provider tells the app that you are indeed the person you claim to be by providing it with a signed ID token.
When the app you’re signing into gets this token, it uses the signature to verify that the identity provider did indeed create that token — and now you’re fully authenticated!
OpenID Connect (OIDC [22]) is the underlying protocol used for this flow. OIDC is a layer on top of OAuth 2.0 — a protocol used for access delegation [23] — that standardizes identity data like your name and email address.
Big tech giants like Google, Facebook, Twitter, and Microsoft all offer identity services that implement OIDC. It's easy and convenient for users since they can leverage their existing accounts.
And it saves a ton of headaches for developers since they no longer need to build secure authentication systems. Instead of authenticating a user yourself, just plug into Google, and you're done!
This is an example of federated identity [24], where a user’s identity is used across multiple systems — Google, Facebook, and all the apps that rely on these identity providers — instead of just one.
However, identity federation has a fundamental flaw: the identity provider can revoke access at any time, instantly locking you out of all sites. You don’t own your identity — it’s stored on Google’s servers, not yours.
You may think it’s rare, but it’s surprisingly common [25, 26] to find posts on Hacker News of users being denied access to their Google or Facebook accounts due to one reason or another, and totally unable to access any service that they used Google or Facebook as an identity provider for.
The quest for true user-owned identity is an active area of innovation which I'll cover in an upcoming post! Subscribe now to be updated when it’s released 👇
Thanks for reading Mindful Musings! Subscribe for free to receive new posts and support my work.
I would love to hear folks’ thoughts on this — feel free to comment below or DM me!
Have a great week all — stay tuned for Part 3!
1 Authentication is, in fact, broader than simply validating your identity — it can refer to proving any assertion, such as your age being greater than some threshold, that you have a valid driving license, or that you have enough funds in your bank.
2 https://en.wikipedia.org/wiki/Cryptographic_hash_function.
3 We can make the hash even harder to crack by adding salt to the original — random data that prevents attackers from using pre-computed data to guess your original password.
4 https://en.wikipedia.org/wiki/Dictionary_attack.
5 About 64% of people reuse at least one password across multiple services: https://auth0.com/blog/what-is-credential-stuffing.
6 https://en.wikipedia.org/wiki/Bot_prevention.
7 There are a few other reasons why passwords are challenging — and many of these are outside of your control as an app developer.
Malware can log the keys the user presses whilst typing the password to secretly exfiltrate it, an enemy (or friend!) could watch over your shoulder as you type it in (this is known as shoulder surfing), and phishing attacks could prompt the user to enter their password when they’re on a similarly branded but malicious website.
8 https://en.wikipedia.org/wiki/One-time_password.
9 https://security.stackexchange.com/questions/112111/capturing-text-messages-on-the-fly.
10 https://messageiq.io/sms-deliverability.
11 https://en.wikipedia.org/wiki/Time-based_one-time_password.
12 https://en.wikipedia.org/wiki/YubiKey.
14 https://en.wikipedia.org/wiki/Hardware_security_module.
15 Your biometrics are stored securely, and never leave your device — they’re only used to unlock the HSM used to store your private keys.
16 https://simplewebauthn.dev/docs/advanced/passkeys.
17 https://blog.google/technology/safety-security/the-beginning-of-the-end-of-the-password.
18 https://auth0-com.cdn.ampproject.org/v/s/auth0.com/blog/amp/our-take-on-passkeys.
19 https://fy.blackhats.net.au/blog/2023-02-02-how-hype-will-turn-your-security-key-into-junk/
20 https://jrhawley.ca/2023/08/07/blocked-by-cloudflare.
21 https://en.wikipedia.org/wiki/Identity_provider.
22 https://en.wikipedia.org/wiki/OpenID#OpenID_Connect_(OIDC).
23 https://en.wikipedia.org/wiki/OAuth#OAuth_2.0.
24 https://en.wikipedia.org/wiki/Federated_identity.
26 https://www.google.com/search?q=lost+access+google+account+site%3Anews.ycombinator.com.
This piece was originally posted on the Koodos Labs engineering blog — follow there for updates on what we’re working on! 😆
Also published here