If you do, you’re really creating an identity system. In the 90s (and even in 2000s), we would think all we have to do is store usernames (or emails) and password hashes, and provide a way to login, logout and reset password. The situation was not helped by ASP.NET membership provider and similar frameworks which defaulted to creating a username/password DBs.
But the world is very grim for such basic systems. For one, accounts will be compromised. Microsoft published in December 2019 that 44 million of Azure AD accounts had a password that was leaked elsewhere. Users will use common passwords that are easy to guess. So you may need to build a mechanism to identify those and require a password reset, esp. if a leaked password is found elsewhere. Although this cause is being helped by browsers (e.g. Chrome, Edge) and OSes (e.g. iPhone) that inform users if they are using a credential leaked in the cyber underworld.
Organizations then need to add MFA, and integrate with various external systems to send text messages. Create software to generate codes and verify. That starts incurring costs, so often there are Authenticator app-based integrations needed. This is all good and fun for software engineers.
But even then there will be phishing attempts and other malicious activity that will cause account to be compromised. Companies like Google, Microsoft, Amazon, Facebook, Okta, and many others use ML solutions to detect if a fraudulent attempt is being made. It’s not perfect science (it’s AI), so there are false positives. Nonetheless, it is enormously useful.
Of course, you will then also need to integrate with biometrics and invest in some sort of WebAuthn implementation, which is vastly more phishing resistant than any other means we know of today.
Identity doesn’t stop here. Once all of this is done, and a user has authenticated, there is cookie and token theft. Those are much harder to detect (although there are some heuristic methods), and mitigate. And it’s almost impossible to prevent theft in practice. You’ll have to start thinking about token binding, i.e. if a token is stolen from a device, it becomes useless. Or that the user has moved very quickly from one location to another (i.e. impossible travel) and invalidate login based on that. If you just invalidate every time a user moves to a new location, then you risk user frustration and a higher drop off rate since most users (if not all) hate authenticating.
And all of this has to be maintained perpetually. This is not a theoretical discussion. I know this from personal experience working in Microsoft’s Identity division. Okta, Microsoft’s biggest competitor, has also pretty much built similar functionality. Likewise, Google and Facebook also have such functionality in their consumer identity systems (gmail.com, facebook.com, etc.). This is just today’s reality.
So, unless your primary business is building identity systems, or you are too large to not have your own proprietary identity system or have some very niche use cases, it’s best to just use one of the platforms built by these companies (or any other reputable company you find). Most of these platform provide a whole lot of customization and continue to innovate in this space.
When it comes to the complexity and challenge in the identity systems, this essay is just an overview. There’s a lot more to identity. That’s why established companies and organizations like Subway, Irish MyGovID, New Zealand Ministry of Education and many others use Azure AD B2C. I know about customers of Azure AD B2C simply because I worked there are a lot, but there are similarly established businesses using Google Firebase, Amazon Cognito, Okta and others.
A few researchers from University of California, San Diego, hired account hijacking services to learn how they went about hacking into email accounts. They published their findings in a 2019 paper “Hack for hire: Exploring the emerging market for account hijacking” in which they explained the process of hiring these black market services, and their observations of how some attackers eventually managed to hijack accounts. I would say it’s a must read for all app developers, esp. those who create or deal with account logins.
In collaboration with Google, the researchers created honeypot Gmail accounts that they asked the attackers to take over. They provided information about the (fake) account owners. The researchers had set up 2FA on the accounts to see how the attackers would overcome that, which some of them were able to.
Summary of attack methodology
Out of the five services, four used phishing to lure their targets and one sent an email with malware.
The attackers created fake Gmail login page and sent the link through phishing emails. Their expectations was that the victims will provide their email address and password on the phishing pages.
The services were sophisticated enough that after harvesting the credentials, they immediately attempted to log in to Gmail using those credentials.
When they unexpectedly hit 2FA prompts, some of the services adapted. They asked the researchers to share the victim’s phone number, and then attempted to phish the victims again. This time, the phishing pages asked the victims to enter the 2FA code sent to their cell phone after the password page. Once the victim entered the codes, the attackers were able to successfully log in.
After successfully logging in, the attackers removed account activity emails that Google sends so the victim will not find out about a potential account takeover. Additionally, they disabled 2FA which would have allowed them to log in again using only the password and not needing to phish the 2FA code again.
The attackers were generally able to craft emails that would bypass Gmail’s spam detection. The researchers state that it is possible that the attackers created a few test accounts and first sent phishing emails to those accounts to check which emails would pass spam filters so those can be sent to the victims.
Why would someone click on the phishing email(s)?
The attackers sent multiple emails with different types of lures waiting for the victims to click on any. Only one click is sufficient for account compromise.
The researchers observed five types of allures: 1) an associate, 2) a stranger, 3) a bank, 4) Google, and 5) a government authority. Usually, they convey a sense of urgency so the victim would immediately click the links in the emails.
Phishing email impersonating Google mimicked a real world warning that Google sends to inform users about device sign-ins.
Some additional thoughts
Even though this paper is from 2019, phishing is still a common method to hijack accounts. And even though accounts with 2FA can sometimes be hacked, it makes it much harder for the attackers to succeed. That’s why major email and cloud providers (Microsoft and Google included) strongly urge users to enable 2FA. As the paper explains, some services just backed out when they learnt that the accounts had 2FA enabled.
In general, luring in victims using time-sensitive threats is perhaps the most effective way to get cooperation. These have included threats of imprisonment or fines from the government, portraying that a loved one is in danger, appearing to provide (shady) services, or simply a message from someone familiar asking to click a link.
The paper also states that Google used forensics from these attacks to identify more than 300 other accounts which may have been attacked in a similar fashion.
As a software engineer who has also worked in the identity and security space, I would say that it’s extremely hard to secure accounts. Securing and protecting accounts requires a lot of investment that is often times unclear to the app developers. It’s not just creating a DB with usernames and password hashes. It’s similar to arms race in which tech companies are evolving more ways to detect and secure accounts, and bad actors are continuing to use whatever is at their disposal to hack into them. So, for most app developers, it’s usually best to either use a third-party identity platform like Okta, Azure AD B2C, Firebase, etc. or just integrate with social logins like Apple, Google and others.
If you are not familiar with the use cases that prompted a
protocol like OAuth 2.0 (often referred to as OAuth2) specified in RFC 6749, understanding the specification will take its toll.
When I first started working on OAuth2, it took multiple readings, discussions
and help from my peers to make sense of it all. In this essay, I attempt to
explain the RFC by walking through real-world examples, which is how I
generally retain information. If you are also trying to understand OAuth2, I
hope you will find this approach useful, too.
Let’s start with an example of a health application that a
user installs from the app store. This application has a social feature which
enables groups of friends to participate in various health activities and
games. The application developer wants to integrate the application with
Facebook to get users’ personal information, such as their name and date of
birth, and their friend list.
The question is, why should the application be allowed to
access the user’s movie list or status? And once allowed, how long will it
continue to have access?
RFC 6749 titled “The OAuth 2.0 Authorization Framework”
addresses these issues so that an application can get access to a user’s data
with their permission.
Definitions
RFC 6749 defines a few roles in section 1.1. I explain all
but one below and will use them off and on in this essay and in subsequent
essays. This will make it easier to understand RFC 6749 over time with its
specialized terminology.
Client: An application is called a client regardless of whether it’s a mobile application, a desktop application or a web application. It is also referred to as third-party application in RFC 6749’s abstract. In our example, the health application running on a user’s mobile is the client.
Resources: Any data that belongs to the user that the client is trying to access would be a “resource.” For example, the user’s date of birth, name and friend in Facebook would all be resources in this context. In the context of OneDrive or Dropbox, a user’s files would also classify as resources. This term is not explicitly defined in section 1.1 of the RFC but I include it here for clarity.
Resource owner: Date of birth, name and friend list are pieces of data that belong to the user who is referred to as the resource owner. These resources will be shared with the client after the resource owner (i.e., the user) consents.
Resource server: The service that hosts the resources (e.g., Facebook Graph) is called the resource server. This does not refer necessarily to a single server and is referred to as HTTP service in RFC 6749’s abstract. On the web, it is often easier to think of the resource server as a collection of APIs that provides access to the resources.
To summarize, we have a client (an application) that
wants to access resources (e.g., name, date of birth, friend list) that belong
to a resource owner (the user).
One way to achieve this is for the client to request
credentials from the user and send them to the resource server, or send some
derivative as proof. The resource server validates the credentials and lets the
application access the resources. Section 1 of the RFC addresses issues with
this approach.
Shortcomings of “Traditional Client-Server Authentication Model”
Section 1 (Introduction) of RFC 6749 talks about
“traditional client-server authentication model” in which the client sends the
username and password in request to the server to access resources. This is how
basic
authentication works, for example, and is shown in Figure 1 below.
Here are some problems and limitations of such an approach.
Third-party applications are required to store the resource owner’s credentials for future use, typically a password in clear-text.
Let’s say that our health application was coded to use basic
authentication. This would mean that the application will prompt the resource
owner to provide their username and password to the application. It will
then send that username and password to the resource server (e.g., Facebook Graph)
to access the user’s data. Either the application will have to ask the resource
owner for their credentials every time it starts, which makes for practically
an unacceptable experience, or it will store the credentials locally so it can
access the resources whenever needed. The client now has perpetual access to
the user’s credentials.
Servers are required to support password authentication, despite the security weaknesses inherent in passwords.
To accommodate the scenario described above, an API such as
Facebook Graph will need to accept passwords in the requests. Every application
that the user downloads requests a username and password. Since Facebook Graph only
gets a username and password in every request, it cannot distinguish between
different applications or even know how many applications are accessing a
user’s data.
Third-party applications gain overly broad access to the resource owner’s protected resources, leaving resource owners without any ability to restrict duration or access to a limited subset of resources.
Since Facebook Graph cannot distinguish between various
applications, and provides access based on username and password, all
applications get the same access. This is because Facebook Graph would have to
expose almost all the resources that Facebook’s own mobile application needs. So,
our health application and Facebook’s mobile application appear the same to
Facebook Graph because they use username and password to authenticate and, therefore,
get same access.
Resource owners cannot revoke access to an individual third party without revoking access to all third parties, and must do so by changing the third party’s password.
This is a corollary of the previous point. In basic
authentication model, because a resource server (such as Facebook Graph) cannot
distinguish between various client applications, it cannot provide a mechanism
for a resource owner to cancel access to any one application. The resource
owner must change their password even to deny access to a single application
and then provide the new password to every application other than the one they wanted
to deny access to their resources.
Compromise of any third-party application results in compromise of the end-user’s password and all of the data protected by that password.
Since every application stores credentials, a compromise of
any one of them means that the user’s password (and entire account) is
now at the mercy of some malicious user. Anyone with the password can change the
password and lock the user out of their own account!
A major shortcoming of basic authentication not mentioned in
the RFC is its inability to introduce additional authentication challenges to
determine the user’s identity. For example, if Facebook decided to add
multi-factor authentication, it cannot easily do so since Facebook Graph is
setup based on username and password. Even if Facebook decided that the client
applications must do an MFA challenge, how would it determine that the
application actually performed that challenge? What additional information can
the application pass to Facebook Graph to prove that the user has passed such a
challenge?
Authorization Server
To address these issues, OAuth2 framework adds an
authorization server in between. The user interacts with authorization server
to prove their identity and determine what access the application should get. This
means that Facebook Graph must not support password-based access at all; otherwise,
applications will attempt to get the password. Facebook Graph must instead use
some other credential or it will not know what access to give to which client.
Therefore, every client, such as our health application, is
required to identify itself and which resources it wants to access when it
redirects the resource owner to the authorization server. Facebook calls its authorization
server Facebook
Login. The authorization server asks the resource owner which permissions
to grant. Then the authorization server creates and issues a new credential to
the client application (we will go into the details of the credential in a
later essay). This credential is created in a way that the resource server,
i.e. Facebook Graph, can validate it was legitimately issued by the
authorization server, i.e. Facebook Login. The credential also contains details
of what the client, i.e. health application, can access. This allows the
resource server to only allow those operations that are permitted to the
client. This is shown in figure 2 on the following page.
The concept of authorization server is not new to OAuth 2.0.
SAML identity providers played a similar role, and Active Directory Federation
Services (ADFS) is an example of an authorization server that relied on SAML.
Authorization server is the fourth role defined in section
1.1 of the RFC.
Authorization server: The service responsible for confirming a resource owner’s identity and getting their approval (aka consent) to share their resources with the client. Upon approval, the authorization server issues a credential to the client that it can use to gain access to the resources from the resource server.
In other words, the authorization server is authorizing the
client to access the resources on behalf of the resource owner. That’s why OAuth2
is called an “authorization framework”.
How does the authorization server establish the identity
of the user so it can determine which resources belong to them? This is when
the authorization server will challenge the user to provide a password, perform
multi-factor authentication, or use other mechanisms to determine their identity.
A malicious user should not be able to declare the identity of any other user
and gain access to their resources.
After the user has authenticated, they may still decline
access to the client for all the resources that the client requested access to.
In such a case, the client is still not authorized to access the resources.
How does the authorization server decide which of the user’s
resources can the client access? After the user has authenticated, the authorization
server informs them of which resources the client is requesting access to. The user,
by virtue of being the resource owner, can then either grant or deny access. In
some implementations, the resource owner can also select which resources they
are willing to share with the client. For example, they may consent to sharing
their name and friend list but deny access to date of birth. This process is also
called approval interaction. Facebook’s such interaction is shown in Image
1 below.
Given the terminology presented so far, now read the abstract of RFC 6749: “The OAuth 2.0 authorization framework enables a third-party application to obtain limited access to an HTTP service, either on behalf of a resource owner by orchestrating an approval interaction between the resource owner and the HTTP service, or by allowing the third-party application to obtain access on its own behalf.” (emphasis mine).
I will cover the second part about application obtaining
“access on its own behalf” in a later essay.
With this, I end the first part of this series. We have
covered until section 1.1 of RFC 6749.