You probably do not need OAuth2 or OpenID Connect. This is a controversial opinion, even more so because my biggest professional achievements are two of the most successful open-source projects in the OAuth2 and OpenID Connect world:
Those two projects helped spawn a company that raised series A and an open-source ecosystem used by millions.
Before reading on, please note that the views stated in this article are my professional opinion. My intention is to save you valuable time developing the wrong things at the wrong time. It is in no way meant to discredit the technology and the brilliant people working on them.
While writing and maintaining Ory Hydra and Ory Fosite, I've spent a good portion of my life working with a large developer community and understanding all the use cases and problems that users encounter when dealing with authentication and authorization.
As a company, we also help others audit and vet their systems. As such, we often see OAuth 2 and OIDC used in the wrong context. This doesn't happen because people make mistakes or don't "get" security. It happens because the protocols are complex and often quite vague.
When used in the wrong context - which often is the case - it can lead to serious security vulnerabilities.
So far, we've identified over 12 cases where incorrect implementation of OAuth2 and OpenID Connect was responsible for low-severity security issues (e.g., logout not properly invalidating sessions) as well as catastrophic vulnerabilities (such as account hijacking with full administrative privilege escalation).
The scary part is that it can happen to any company of any size - regardless of how talented their developers are!
If the only tool you have is a hammer, you tend to see every problem as a nail. - "Maslow’s Hammer"
At Ory, we don't sell OAuth2 and OpenID Connect as the "holy grail" solution. While the two are very powerful protocols when used correctly, and have many advantages and use cases, the truth is that they are not always necessary. In fact, you most likely do not need them.
Before we continue, be aware that there are two primary use cases where you are implementing OAuth2/OpenID Connect, and that this article only covers one of them:
Being a consumer of OAuth2/OpenID Connect: you implement "Sign in with Google," need access to a user's GitHub account, and other use cases. Here, OAuth2 makes a ton of sense. This article does not cover this use case.
Being a provider of OAuth2 / OpenID Connect: You want to become the system that issues OAuth2 Access Tokens, OpenID Connect ID Tokens, etc. This article is for you!
Now that we've set the scene, let's look at the structure of this article. Similar to OAuth2 and OpenID Connect (and accompanying RFC8252, RFC6819, RFC7636, RFC8628, RFC 7523, OpenID Connect Discovery, OpenID Connect Session Management, OpenID Connect Front-/Backchannel Logout, ...) this article is lengthy because OAuth2 and OpenID Connect span a variety of specifications and regularly spawn new, innovative ones.
Let's look at the main points covered in this article.
A tl;dr decision tree to help you choose whether you need OAuth2 or not.
When are OAuth2 and OpenID Connect useful and what are their problems?
What misconceptions do developers have about OAuth2 and OpenID Connect?
When should you avoid OAuth2 and OpenID Connect?
Before we dive deeper, I want to stress two points:
We have the utmost respect for the amount of innovation spawned by researchers and contributors in the area of OAuth2 and OpenID Connect. This article does not try to diminish their impressive work. Rather, we intend to shift the mindset of small and medium-sized teams from "we need this complex security thing" to "we can solve this complex thing at a later stage and use something really simple for now!"
Ory is developing an open-source identity infrastructure service called Ory Kratos which is available as a managed cloud service. This article discusses many of the reasons that initially motivated us to develop this new system. So yes, it's a shameless self-plug. But also: it's what we believe! There is a place in the software world for a secure, reliable, scalable, and simple solution. Our goal at Ory is to develop an open standard, with the help of a brilliant community. We want something that works for 80% of use cases.
Let's agree that it's generally an excellent idea to use an existing piece of software to solve login, registration, user management, password, reset, account recovery, 2FA, and all topics related to user authentication and permission management (authorization).
Today, most technologies rely on OAuth2 as the primary interaction protocol. And as we will learn in this article, it is most often too complex with too many edge cases.
But that doesn't mean that there isn't a better way to solve these problems! Ory's attempt at building a better identity authentication de-facto open standard is the Ory Kratos Open Source project!
A generic decision tree to help you decide whether you need OAuth2 or not can be summarized as follows:
Since you're reading this on the Ory website, and we believe in the products we build, you will find those prominently in the above graph. For comparable products, we have collected the following open-source ones:
If you need both an identity platform, implying that you most likely need to migrate your user data as well, and an OAuth2 & OpenID Connect service and want to use OAuth2 / OpenID Connect for everything, check out RedHat's Keycloak. KeyCloak is as if you were to use Ory's Identity Platform and Ory's OAuth2 / OpenID Connect platform together, while only using OAuth2 & OpenID Connect to "authenticate".
If you only need an identity platform without OAuth2, there are a few alternatives to Ory's Identity Platform / Ory Kratos:
Language-specific libraries such as authboss with the ups and downsides language-specific libraries have.
Supertokens - we no longer endorse them due to a questionable choice of mixing OAuth2 terminology with self-built protocols. In particular, the use of OpenID terminology without supporting the specification itself, and the use of "access" and "refresh tokens" in incorrect contexts. We believe this to be what Scott Brady calls MyOwnOAuth™.
OAuth2 states its primary use case in the first section (abstract) of its RFC:
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.
OAuth 2 was invented to address a need in the era of Web 2.0. Platforms such as Google and Facebook have massive troves of data, and they wanted to open this data up to third parties.
This was sometimes used benevolently (e.g., Facebook Games) and sometimes abused (e.g., Cambridge Analytica Scandal).
Furthermore in 2012, "Social Login" was all the craze. (And still is!)
OAuth2 and OpenID Connect were born to solve these two fundamental use cases:
Allow third parties access to your e.g., Facebook data (OAuth2).
Allow third parties to authenticate you via your e.g., Facebook account (OpenID Connect).
If you are new to OAuth2, it might be confusing why you need OpenID Connect. The two are interlinked yet different. The main distinguishing factor is the audience of the resulting tokens:
OAuth2's tokens (OAuth2 Access Token) have the first-party server as their target audience.
OpenID Connect's tokens (ID Token) have the third-party client as their target audience.
OAuth2 is not Authentication. Access tokens are not sessions.
OpenID Connect was invented, or added on top of, OAuth2 because OAuth2's tokens are opaque - not transparent - to the third-party clients who receive them.
They serve no purpose except for presenting them to the first-party server (e.g., GitHub) and expecting a response (e.g., the user's private repositories).
OAuth2 is not an authentication (login) protocol! The purpose of OAuth2 Tokens is to authorize requests at a first-party server (or API). If the third party uses the OAuth2 Access Token as proof of authentication, an attacker could easily impersonate a legitimate user.
Bob creates the app "myphotobook" which offers "Sign in with Facebook." It accepts access tokens as proof of authentication at https://myphotobook.com/login?access_token <facebook_token>
.
Eve creates the app "myevilapp," also offering "Sign in with Facebook".
If Alice uses both apps, Eve can use Alice's access token from "myevilapp" to impersonate her at Bob's app by calling; for example, https://myphotobook.com/login?access_token=<alices_myevilapp_facebook_token>
.
There are ways to prevent this attack using standard OAuth2 mechanisms. Ensuring that the access token was initiated by "myphotobook" is an effective mitigation. But it requires in-depth knowledge of the protocols involved, and it is implicit. In security, we really don't like implicit.
OpenID Connect addresses this by introducing a new type of token, along with additional configuration parameters, which can then be used by the third party to reliably and securely authenticate users.
That is why OpenID Connect ID Tokens are transparent - usable - to third-party clients; it's because they are JSON Web Tokens that can be decoded. Additionally, ID Tokens contain the audience - in this case, the third party, not the first party.
That way, "myphotobook" can check whether the audience is "myphotobook" and not "myevilapp":
// Example ID Token Payload
{
iss: "https://myphotobook.com",
sub: "alice",
// OK:
aud: ["myphotobook"],
// NOT OK:
aud: ["myevilapp"],
exp: 1516239022,
iat: 1516239022,
}
By now, you've read "third party" quite often. And that is the catch! These protocols are targeted at third-party integration. Meaning that someone else is trying to access your user's data. And someone else is trying to authenticate their users using your data!
Your first point of decision is exactly this. Are you building a system that interacts with third parties, such as partner networks and platform APIs? Or are you yourself becoming the next "Sign in with ..."?
If so, OAuth2 and OpenID Connect are the best-in-class protocols to address your use case!
Regardless of whether you are building a mobile app, single page app, web app, native app, an API system, or you need Bearer Tokens or JSON Web Tokens. You most likely do not need the complexity of OAuth2 and OpenID Connect right now.
What you most likely need is a system that offers a variety of methods to authenticate users (password, passwordless, FIDO2, biometrics, SMS, ...), has profile management, account recovery, password reset, email and phone number verification, and so on.
You can always add OAuth2 and OpenID Connect on top of your existing authentication infrastructure using tools such as Ory Hydra.
There are, of course, exceptions to the rule. In some cases, it makes sense to start with OAuth2 and OpenID Connect right away.
This can be the case for huge businesses such as Apple or Amazon that have hundreds of thousands of employees across hundreds of teams, organizations, and offices.
In these cases, office A (Google Mail) might not trust office B (Google Earth) to access data without user consent. Large organizations can afford the extra cost of training, implementation, testing, and maintenance of complex systems.
But even then - sign in to your Google profile, and you will not find OAuth2 or OpenID Connect but instead, a regular login flow: Post the HTML form to a server, and receive a cookie!
A counterexample of this is Amazon which uses OpenID Connect to sign you into their Amazon store.
Another exception is if you plan interaction with a lot of different client types. This, in particular, applies to clients that do not have a traditional user interface (think Fire TV Stick, Chromecast, or anything that does not feature a keyboard/USB port or browser).
In this case, you can benefit from the work done by the IETF and OpenID Foundation which have defined interactions for these types of clients.
Some examples of niche clients could be:
Adding OAuth2 and OpenID Connect to your system, where you're not using it as the primary authentication system for your own services, is beneficial if you use other tools and services which should use the central identity platform to authenticate users.
Examples of these tools could be:
In these cases, you have to weigh whether you want to use one authentication methodology for first parties and another one (OpenID Connect) for third parties, or whether you want to use OpenID Connect for both.
This decision depends a lot on context, but neither of the two options is more or less difficult to implement.
An exception to the rule is if you plan to add OAuth2 to your system to service third parties. In this case, you might save some time and effort by using OpenID Connect and not having to implement two authentication mechanisms.
The decision here is not clear-cut and depends a lot on context. It can be as difficult to implement two authentication mechanisms for first and third-party use as implementing only OAuth2 for first and third-party.
Making things hard to understand and implement never improves security. It makes systems less secure since there are more things that can (and eventually will) go wrong. OAuth2 and accompanying specifications are plentiful, difficult to read and understand, and sometimes vague.
When interacting with popular OAuth2 and OpenID Connect providers (Facebook, Google, Amazon, ...) you will observe that everyone solves things a little differently.
GitHub does not offer OpenID Connect capabilities, Facebook has its own flavor of OpenID Connect, Apple had to receive an open letter from the OpenID Foundation to become compatible, and Auth0 has its own OAuth2 quirks... the list goes on and on!
OAuth2 and OpenID Connect have spawned an enormous and exciting amount of innovation.
That said, if you're a small team or have a very clear use case (e.g., an app), the amount of reading needed to properly interface and use the correct OAuth2 methodologies is vast - even if you are only interfacing with OAuth2 servers and using OAuth2 libraries!
Let's take a look at the most popular OAuth2 extensions. The list is long, not (only) to make a point, but because there are so many variations and possibilities:
This list is long, but there is an even longer list of draft and active development RFCs and specifications. Alongside these more generic specs, there are also specs for financial institutions (FAPI), governments (iGov), EAP, MODRNA, and many more.
So the question remains: do you really need all of this? Or should we simply solve login and move on until we actually need to address a critical need?
If we haven't convinced you yet, let's take a look at some more arguments for avoiding OAuth2 and OpenID Connect. As stated earlier, this is a recommendation and does not apply to valid use cases. But, before you go down the OAuth2 rabbit hole, you should know exactly how far it goes.
User-facing OAuth2 and OpenID Connect flow require the user to interact with a website in a browser. If you are building native apps, you can not circumvent this, and you will need to open the iOS or Android browser.
The only way around this would be to use the disgraced OAuth2 Resource Owner Password Credentials Grant. But this grant is not available on all platforms, will raise red flags with security auditors, and is scheduled to be removed from the specification with OAuth 2.1.
For many first-party apps, this is an absolute no-go, because your users will leave the app to open a browser and sign in, breaking the user experience. However, some platforms like iOS try to improve this with better UI. Still, many product owners shy away from this practice.
If you do want to use OAuth2 for native apps, have a look at our OAuth2 for Native Apps guide.
Neither OAuth2 nor OpenID Connect is designed to manage sessions. A few years ago, when I was two weeks deep into implementing Auth0, I realized that Auth0 has three different session layers which all have their own logout mechanisms!
That's not because Auth0 is bad. It's because they are using a third-party protocol to solve a first-party problem! Citing from their session layer documentation:
Application Session Layer: This layer is the session inside your application. Though your application uses Auth0 to authenticate users, your application also tracks that the user has logged in to your application; in a regular web application, for example, you achieve this by storing this information inside a cookie.
Auth0 Session Layer: Auth0 also maintains a session on the Authorization Server for the user and stores their user information inside a cookie. This layer is used so that the next time a user is redirected to Auth0 for login the user's information will be remembered. This session layer makes the SSO experience possible for inbound SSO implementations.
Identity Provider Session Layer: When users attempt to sign in using an identity provider such as Facebook or Google, and they already have a valid sign-in (with whichever provider they choose) they will not be prompted again to sign in though they may be asked to give permission to share their information with Auth0 and, in turn, your application.
Put in pseudo-code, and you get something like this:
const { access_token, id_token } = oauth2.exchange(req.query.code)
const session = {
user_id: id_token.sub,
access_token,
}
// We still use a cookie to create the session
writeSessionCookie(session)
})
app.get("/protected/api", (req, res) => {
// Notice: we do not care about the token here!
const session = readSessionCookie(req)
const user = getUser(session.user_id)
if (!user) {
res.redirect("/login")
return
}
})
app.get("/logout", (req, res) => {
const session = readSessionCookie(req)
const user = getUser(session.user_id)
if (!user) {
res.redirect("/login")
return
}
// This is the OAuth2 session layer.
oauth2.logout(user)
// This is the application session layer.
deleteSessionCookie(req)
res.redirect("/login")
})
You probably know CircleCI, it's a continuous integration platform that runs tests on your code. Naturally, CircleCI needs access to your GitHub or GitLab repositories which means it uses GitHub's / GitLab's OAuth2 capabilities.
The catch is if you signed in to CircleCI via GitHub, and you log out of GitHub, you will still be signed in to CircleCI! The same applies if you use an app using "Sign in with {anything really}". With OAuth2, it does not matter to the third party what your session status is at the first party!
At Ory, we get (and answer) this question on a regular basis: "I have implemented OAuth2 but how do I globally sign out my users?". There is a specification for that! But it could be so much easier.
To cite Auth0's logout documentation, there are three separate logout mechanisms when delegation protocols (OAuth2 / OpenID Connect) are involved:
- Application Session Layer Logout: Logging users out of your applications typically results in their application session being cleared, and this should be handled by your application: for the Application Session Layer, there is nothing within your Auth0 tenant that you need to use to facilitate session termination. This will require you to utilize whatever application session stack you are using to clear out any session-related information. Note that some of the Auth0 SDKs do provide some support for application sessions; please check the documentation to see if there is any local SDK session removal that needs to be done.
- Auth0 Session Layer Logout: You can log users out of the Auth0 session layer by redirecting them to the Auth0 Logout endpoint so Auth0 can clear the SSO cookie.
- Identity Provider Session Layer Logout: It is not necessary to log the users out of this session layer, but you can use Auth0 to force the logout if required.
Browsers have three principal ways to persist data on the client:
Cookies which only can be set by the server (httpOnly
).
Cookies that can be set by the client (JavaScript's document.cookie
- vulnerable to XSS).
LocalStorage which can be set by the client (JavaScript's localStorage
- vulnerable to XSS).
When receiving an Access, Refresh, or ID Token, the question is where do you store these? The natural place would be in the browser's httpOnly
cookie! But many applications today are client-side and do not have direct access to a web server.
So, storing it in a httpOnly
cookie might be the most secure variant, but it is also the most inconvenient one!
There are several opinions and considerations to be made. We've collected a few of the most interesting discussions for further reading:
OAuth2 has strict security measures in place to mitigate attack vectors. The most common is "Token Replay" mitigations, in particular for OAuth2 Refresh Tokens and OAuth2 Authorization Codes.
Token Replay mitigation means that if you use the same Refresh Token, or Authorization Code twice, the request will fail and all associated access, refresh, and authorization codes will be invalidated. This means that your user has to redo the whole OAuth2 flow from the beginning!
And unfortunately, this happens quite a lot. It can be a bug in your system, or it can be two services making a request at the same time with a token that needs to be refreshed.
Preventing these types of race conditions is challenging in front-end applications - you will need a good side effect solution such as Redux Saga to ensure that only one API call is refreshing the token at a time.
In distributed systems, this becomes even more difficult!
Of course, there are solutions to this problem as well, such as having a grace period (e.g., a few seconds) where the refresh token can be reused. Yet, it adds another layer of complexity and potential flakiness to your system.
What happens if your system is under heavy load? Will the racy refresh requests be fast enough? What happens if things time out? Or if you need to retry? Should you deactivate token reuse detection completely? But then you might fail the security audit!
There are no clear answers to this. It takes time, research, testing, and iteration to make the right decisions.
In development, we always think about building scalable systems. Yet, one of the most difficult aspects of scaling a software system is the human aspect.
Google invented Golang, a novel programming language to address the problems of scaling the humans that write the software because it is as difficult to scale from 10 to 20,000 developers as it is to build a search engine that replies to any query in less than 50 ms wherever you are on the planet.
I started Ory a few years back and have since scaled it from a one-man show to a company of almost 30 people (a comparatively small number). Throughout this, I've witnessed the community grow and struggle with OAuth2 and OpenID Connect terminology, complexity, and tooling.
In hindsight, it's clear that scaling development (the human side) with complex protocols is challenging since there is a high barrier to entry and a steep learning curve.
What's the difference between a resource server, a resource owner, a third-party client, a relying party, an ID token, the refresh token, and the PKCE verifier?
All of this has to be taught and understood by any new team member touching these systems. Sure, there are tons of resources out there. But which one applies to your system?
A good question to ask yourself when deciding whether or not you need OAuth2/OpenID Connect is whether you want users to see a screen similar to the following one:
If the answer is no, you do not need OAuth2 / OpenID Connect for user authentication.
OAuth2 tokens have a scope. The scope is usually something like read:user
or profile:write
. The OAuth2 scope does not say what a user can and cannot do.
OAuth is not suitable for user authorization. The fact that you have an access token that allows you to act on the user’s behalf does not mean that the user can perform an action. Source
An access token represents that the client application has been authorized by the user. It states what a user said (consent!) a third-party application can do in their name. Let's take a quick look at the OAuth2 flow:
The client application asks the user if they can access a protected resource on their behalf (by redirecting the user to the authorization server’s authorization endpoint, specifying exactly what they would like to access (scopes)).
The user identifies themselves to the authorization server (but remember, OAuth is not authentication; you’ll need OpenID Connect to help you with that).
The user authorizes the client application to access the protected resource on their behalf (using the OAuth consent process).
The client application is issued an access token.
For example:
Alice allows myphotoapp to access her Facebook photos.
Bob allows mytodolist to access his Google Calendar.
Let's make a counterexample:
If Alice would allow myphotoapp to act as an administrator of the system and delete the production database, it would not matter unless Alice is actually a system administrator. Similarly, Alice can not allow myphotoapp access to Bob's pictures, because she is not Bob.
I have lost count of the number of times developers have gotten this wrong. And again, it is not because people are not skilled. It is because complex protocols have steep learning curves, and ain't nobody got time to learn everything. As said before, complexity kills security.
Specifications are subject to change and deprecation as in any other system. RFCs and specifications are not timeless and do not protect against breaking changes, upgrading systems, changing configurations, or deprecating specific flows.
Here are some examples of deprecated specifications:
There are of course more abandoned and deprecated specifications. However, those never made it past the draft status and I, therefore, didn't include them. For this reason, we only accept finalized RFCs and specifications to Ory Fosite and Ory Hydra!
In this section, we have collected questions and statements we have seen and read over the years. We hope that our answers can help you convince your peers to go one way or the other!
OAuth2 access tokens are a subcategory of tokens. An authorization token is just a string that is used to authorize a request. It can be anything from a JSON Web Token (so-called pass-by-value tokens) to a random identifier (so-called pass-by-reference tokens).
You do not need OAuth2 to issue such a token!
In particular, you will find terminologies such as "Personal Access Tokens" or "API Keys" in the wild. These types of tokens are not the result of OAuth2 flows! They use systems like Kong's Key Authentication Plugin or a simple service.
You only really need OAuth2 and OpenID Connect if you'd like your users to give consent ("i.e., I want to allow this app access to my personal data"). You do not need OAuth2 to generate a JSON Web Token, a Personal Access Token, or a Native Mobile App Session Token.
At Ory, you can use Ory Session Tokens if you want to interface your API with native apps and clients which do not have a browser.
We are also planning to publish a new token service that will standardize API Keys and Personal Access Tokens so that your users will be able to easily create these types of tokens in a scalable and secure way!
In conclusion: OAuth2 issues tokens. But not every token must be issued by OAuth2 to be secure.
You can use JSON Web Tokens without OAuth2. These are two different standards that can be used independently! See the section above.
In some cases, this is a valid point. If you are facing a complex landscape with hundreds of different services and integrations, it may make sense to standardize everything using OAuth2 and OpenID Connect.
This is especially true if you can build a dedicated team that is responsible for training, auditing, developing, and maintaining your OAuth2 and OpenID Connect services and integration tools.
Another valid case is if you are building a platform wherein third parties need access to your system. Here too it might make sense to start using OAuth2 and OpenID Connect immediately to maintain one standard method of authentication.
If you are a small team, a start-up, or a company with a single product you will not need it unless you fall under one of the exceptions listed in this article. And if you still decide to go this route, be prepared to spend a considerable amount of time and development resources to get it right.
This is something we hear a lot from consultants! At least OAuth2 and OpenID Connect are standardized and have integrations for most programming languages.
But wouldn't it be nicer if we actually had something that:
Addresses the need for a solution that solves login, registration, account recovery…
Is secure and not incredibly complex
Solves permissions (not OAuth2 scopes) in a scalable and reliable way
Is standardized
Can be extended to also support OAuth2 and OpenID Connect when you really need it?
That's exactly why we are building Ory. We want to build the next generation of authentication and authorization services. While there is still a long way to go for Ory to really become a new standard, we already have a solid foundation!
While you're here, maybe one of our projects will spark your interest:
Of course not! OAuth2 and OpenID Connect are extremely powerful and well-designed protocols that can, if used in the correct context, greatly improve the interoperability of systems and thus reduce complexity (which increases security).
There are many examples of these two protocols being used securely and successfully. From FireTV sticks to integrating different services (Hubspot, Google, Discourse, ...) and offering powerful tools to provide third parties access to sensitive, private information.
But it is important to keep in mind what you want to build and in what sequence. For many of the use cases that end up in Ory's community, something like Ory Kratos and Ory Network are a much better fit than trying to implement OAuth2 and OpenID Connect both on the server and client side!
And the great news is, if the need for OAuth2 and OpenID Connect arises, you can use Ory again to add these on top!
Thank you for taking the time to read this article. It's a lot, I know!
I hope you learned a thing or two and, more than anything, that you now have the knowledge to start building your system without spending restless nights on things you don't need yet, figuring out all the intricate details of OAuth2 and OpenID Connect!
Do you have a different opinion on this topic or did you just enjoy the read? Write me on Twitter, or follow me on GitHub.
Thank you for reading and see you next time! Maybe in the Ory Community? :)
Also published here