Introduction
Oauth and OpenID Connect are quite confusing topics. This blog article came about because of several conversations I’ve had with colleagues and customers asking for better understanding.
This isn’t rocket science, but the beginning is certainly confusing. So, this article will attempt to make things a bit clearer. To do so, at times I will simplify things, that may come at the cost of 100% accuracy but it helps understand it better.
AAA
The technologies we’re dealing with here, are part of the AAA domain. This term (Authentication, Authorization, Auditing) isn’t as commonly used any more as it used to, which is odd given that current technologies distinguish much more cleanly between them.
- authentication: this refers to confirming you are who you say you are. While, the whole username/password process is authentication and serves that purpose. It is hardly the only time this comes into play. In fact, there’s a real movement underway to do away with passwords (something for another article). If you show up at a bar and show your driver’s license to a bouncer. The bouncer will compare the phone on the license to you, and check whether or not the license looks fake. That is authentication.
- authorization: this refers to granting you permissions to do something. Typically, this comes after you have successfully authenticated. This is a very different process from authentication and nowadays is often performed by completely different systems. In our bar/bouncer analogy, this refers to the bouncer letting you in or not (based on whether or are old enough). Thus, authentication will not automatically lead to authorization, but commonly, authorization will require proof that authentication was successful.
- auditing: this is recording your actions such as potentially failed and successful authentication as well as authorization. This is really important and also often done badly. I have seen companies record EVERYTHING. Great, this will record such an abundance of information that you will not find what you are looking for any more. So, some care is necessary.
Now in the contact of this article, I am not going to address auditing any further, but authentication and authorization will play a vital role.
The High Level flow
Let’s say you want to use the Bing Bong app. That’s a fictional app with a button that goes ping. Really useful in operating theatres and the like.
The Bing Bong app requires you to register and/or login and presents you with a couple of options such as a Microsoft Account, Google or Facebook. You select the Microsoft account and are prompted to login, after that has completed you can start using the app.
In this fictional scenario, Microsoft is authenticating you, and the Bing Bong app is authorizing you. The Bing Bong app decided the trust Microsoft (and Google and Facebook) with the authentication. There’s fair bit of steps involved in that and they are not relevant here.
Now, you use the Bing Bong app in a browser, and you previously opened up your outlook.com mailbox and signed in with your Microsoft Account. While the Bing Bong app will ask you click a button indicating that you are using a Microsoft Account, Microsoft may not actually prompt you for a password (or the passwordless authentication flow). Why? Because you already authenticated when logging in to outlook.com.
That behaviour is not unique to Microsoft Accounts, Google and Facebook support the same behaviour. This is OIDC and OAUTH at work. The authentication and authorization are two very different actions performed by different parties.
OAUTH and OpenIDConnect
So, let’s get to our main topic. OAUTH (strictly speaking now OAUTH2) and OpenIDConnect are related. OAUTH is the older of the two.
OAUTH is only focussed on authorization. It’s been around for a while, and because it doesn’t really handle authentication, several companies built their own authentication system on top of it. To avoid too much fragmentation, the industry eventually went back and standardised an authentication approach built on top of OAUTH and called it OpenIDConnect.
OpenIDConnect is a proper standard and compliant implementations should easily work together. Some authentication systems look a lot like OpenIDConnect but aren’t 100% the compliant (like sign-in with Apple).
OAUTH
So, let’s talk about OAUTH first. OAUTH is an authorization framework and probably the easiest way to understand it is as follows:
Imagine a rest API. This REST API lives at https://myapi.somedomain.com. When an application (and that can be a browser based app) wants to connect to this API, they will send a typical GET or POST request and get a reply. The POST request may contain some JSON in the body, and reply typically looks like some JSON too. That’s all fine.
However, if the API is configured to require authorization, it will need to be configured with an identity system (such as Azure AD, Azure B2C, IdentityServer, Cognito, etc.). In .net core this is quite trivial. The configuration will contain a reference to a domain (who’s is authenticating), a client ID (this is typically a unique GUID for that app) and some settings. While there’s a manual way to configure this, most identity systems will provide you with an easy SDK to configure this. But I digress…
Your API, if it is configured for authorization, it will expect not just a GET or POST request, but it will also require a bearer token to be added to the header. The most common type of token we use is called JWT (Javascript Web token). This is actually a bit of clear text that has been base64 encoded. So, if you were to do a traffic capture of the request you will see this header.
The API does not only require this JWT, it also requires it to be valid. If not, the API returns an error. So, if you were to inspect a token (we will do that later) you will see that the token contains several pieces of info and these pieces are important. Those piece are called ‘claims’. Effectively, when sending a token you are claiming some things, but they aren’t yet confirmed. We could have called those attributes or properties and you can think of them in those terms as well if that makes it easier to understand.
Here’s a token
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ii1VanR2RzhheGt4YjQ5QlpZLVVyZHhtTGd3Zkw2eHlfa1dhMm1pbnppcjQifQ.eyJleHAiOjE2MTEzMDA4MTksIm5iZiI6MTYxMTI5NzIxOSwidmVyIjoiMS4wIiwiaXNzIjoiaHR0cHM6Ly92YW5iZXNpZW5vbmxpbmUuYjJjbG9naW4uY29tLzUzNzdlYjUzLWU4NzMtNGQ2ZC05ZWMzLWQyOTc2NjZmYjJkNC92Mi4wLyIsInN1YiI6ImI1YjJlY2E1LTNlYTktNDcyMi04ZTUzLTUwODczMWYwYzM2MSIsImF1ZCI6IjVhMDZiMzIzLTM2OGItNGU2Yy04OTQ1LTdhZGMwYTUxZDAyMSIsImFjciI6ImIyY18xYV9zaWdudXBfc2lnbmluIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjE2MTEyOTcyMTksImF1dGhfdGltZSI6MTYxMTI5NzIxOSwidGlkIjoiOGQxZGExZjUtOTA0Yi00ZDliLWJmMWItY2I4N2I5ZjU3N2UzIiwiZ2l2ZW5fbmFtZSI6IldhcmQiLCJmYW1pbHlfbmFtZSI6InZhbiBCZXNpZW4iLCJuYW1lIjoiV2FyZCB2YW4gQmVzaWVuIiwiZW1haWwiOiJ3YXJkQHZhbmJlc2llbi5jb20uYXUiLCJpZHAiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vOGQxZGExZjUtOTA0Yi00ZDliLWJmMWItY2I4N2I5ZjU3N2UzL3YyLjAiLCJpc0FkbWluIjp0cnVlLCJpc0VuYWJsZWQiOnRydWUsImRlZmF1bHRMaWJyYXJ5IjoiIn0.rZucLdTxZrWvuAYWHI-WtUL5Rcn_43JUxVODBIbHUJUxw8V_IRPW-48I-MGRkPmrXOJTotBaouJqvreQ5tPbNxUbCXFy7YyXPkTCFdgmLitax-v-mgO6FlTNFWCnn_Vm5FCFXgJbxwdfu9AOyzE_lxNSGehdePEwcsINcnJoxh7fL11Kxhp_sUa89YsmJToPCT9zA9NfbA4D7bk-7aKGlhClNQWzeDnzS5Xz2GeDZ23o-ESeGhgzIE4XKDin_BXhFO2UcbeN8UHWf-hTF2XmNZYX_xCnY5UTzBsVuomjYe_DfmEXl1MkA3NyEq3JzGWtOYmHvuryIxphl6HrKjxjdw
And if you want to convert that go to https://jwt.ms, paste it and you’ll get:
{
"typ": "JWT",
"alg": "RS256",
"kid": "-UjtvG8axkxb49BZY-UrdxmLgwfL6xy_kWa2minzir4"
}.{
"exp": 1611300819,
"nbf": 1611297219,
"ver": "1.0",
"iss": "https://vanbesienonline.b2clogin.com/5377eb53-e873-4d6d-9ec3-d297666fb2d4/v2.0/",
"sub": "b5b2eca5-3ea9-4722-8e53-508731f0c361",
"aud": "5a06b323-368b-4e6c-8945-7adc0a51d021",
"acr": "b2c_1a_signup_signin",
"nonce": "defaultNonce",
"iat": 1611297219,
"auth_time": 1611297219,
"tid": "8d1da1f5-904b-4d9b-bf1b-cb87b9f577e3",
"given_name": "John",
"family_name": "Doe",
"name": "Jonh Doe",
"email": "john@somedomain.com.au",
"idp": "https://login.microsoftonline.com/8d1da1f5-904b-4d9b-bf1b-cb87b9f577e3/v2.0"
}.[Signature]
There’s a bunch of useful info in here, and no, you can’t use this one to connect to anywhere as will be obvious rather soon. Also, this is a temporary account.
- exp: a token has a short lifetime. With Azure AD this is typically 1h. (what happens when that expires, bear with us, I’ll explain)
- iss: who issued this token. Here you can see this is coming from an Azure B2C instance.
- sub: this is me. As in, this is the GUID of the actual user account in the identity system
- aud: this is the GUID of our API.
- idp: who actually authenticated this user
- signature: an encrypted hash
So, what do we learn from this:
- a token isn’t very long lived. This is by design. If an account is disabled, the max amount of time access will be available will be one hour.
- a token is issued for a very specific target application. So, each API, each service etc will each require their own token. It’s important to understand that concept because otherwise there’ll be blood and tears when configuring this. A token is typically for one single endpoint. Remember, we are talking authorization here, not authentication. The fact the a lot of access tokens are used, does not mean you need to re-login for each of them.
Another interesting detail, this token was issued by an Azure B2C instance. However, the user who signed in, was actually a federated user, in this case an Office 365 account. So, when this user signed in, they went to Azure B2C which sent them to Azure AD for authentication which then sent them back to Azure B2C which issued the access token. So, here Azure B2C issued the token, but didn’t authenticate the user either and instead decided to trust Azure AD. This is VERY common with OAUTH and OpenIDConnect.
If this user used Facebook, the idp field would point to facebook.
Now what about the signature… This is a key element. At the beginning of our token, we can see a typ claim (specified that this is a JWT), a alg claim (specifying RSA 250 was used to sign), and a kid claim, which contains the thumbprint of the public key whose matching private key was used to sign this.
So, the signature is essentially a hash, that is RSA256 encrypted using the private key whose public key thumbprint matches the KID.
So, where’s this key? Obviously this key is on the identity service (in this case Azure B2C), and unlike with SAML, we don’t need certificates to install it. Our API knew where to find it based on this *iss claim.
Each identity service will run a discovery endpoint where applications can find out the required info to validate a token. If the validation fails, authorization fails.
a token can include additional claims for example listing an address, group membership etc. This information can be used to implement policies and additional conditions.
So, in conclusion, while your API requires a token, it doesn’t care how you got it, as long as it’s valid and that being valid does depend on compliance with a couple of settings in the API.
Implementing this in an .net core API is trivial. It’s one of the nice things about OAUTH, it’s easy to implement on the server side. (but confusing to understand what’s going on in the first place :) )
OpenID Connect
So, when using OAUTH, you require a JWT in order to access the services and data exposed by the API. So, how do we get this JWT in the first place. This is where OpenID Connect (or OIDC) comes in the picture.
OIDC is a public open standard and is what Microsoft uses in Azure AD and Azure B2C. This widely used but not every implementation that issues JWT will use OIDC for the authentication. However, while other systems may different, the overall functioning is largely the same.
So, let’s dumb this down to make it easier to understand.
what follows isn’t 100% accurate, has been simplified and doesn’t take into account the differences between the different ‘flows’. More on that later. Also some of these steps may be done for you when using a Windows 10 PC that’s joined Azure AD, etc. So, there’s no one size fits all, but the principles stay the same.
Say you want to use an application and are prompted to authenticate. Outlook.com is a good example because authentication is handled by live.com rather than outlook.com.
Most people don’t notice this, but you can actually easily recognize what’s going on by paying attention to the URL in your browser. (I am assuming that you are NOT signed in to a live.com account as otherwise, step 2 in the list will be your inbox…)
- go to https://outlook.com
- immediately you are referred to https://outlook.live.com/owa. This is a different domain, this is where outlook.com lives.
- click on sign in
- now you go to https://login.live.com. This is the actual authentication service. If you look at the URL, there’s quite a lot going on. All the parameters specify to live.com (which is used for tons of stuff, including XBox etc) what you are authenticating for. Live.com knows what you are asking authentication for.
- authenticate.
- you get to your inbox
- now, if I go to my Xbox account, I can see that without signing in again.
So, what happened between 5 and 6? What happened was so fast that will not see it happen.
https://login.live.com basically responds with an authorization code. Your browser kindly remembers that authorization code. This code proves that you successfully authenticated. Nothing more, nothing less. This code is typically long lived (several days, at least, sometimes longer). The authorization code is issued by the authorization endpoint. This is an actual URL on the live.com domain.
With this code, your browser will now go to the token endpoint. That is another endpoint on the live.com domain. From the token endpoint, we request an access token for a particular application (outlook.live.com in this case) and provide the authorization code as proof. The token service will return a JWT with validity of 1h.
Refresh tokens
Because the token is only valid for 1h, when it is about to expire, it needs to be renewed. As our authorization code is still valid, our browser can simply go and do that.
when I say browser, it’s really typically a bunch of JavaScripts in the website you are viewing that does this. Depending on what is used to build the site, different technologies have different implementations. For Azure AD and Azure B2C, you can use the MSAL library which will handle most of this activity and the user is none the wiser. A typical method is to use iFrames or refresh tokens. However, be aware that this is happening quite frequently.
Mobile apps will always use a refresh token. How this works is fairly simple, but it only involves the token endpoint on the identity service.
If a refresh token was requested (more later), rather than return an access token, the token endpoint returns two tokens: the access token (validity 1h) and a refresh token (validity something like 6 months). Every time the token needs to be refreshed, the refresh token is used, and a new access token AND new refresh token is issued. With mobile apps, you can expect this to happen pretty much every time you open the app…
So, why bother with the refresh tokens? Quite simple really, they avoid users having to login repeatedly, which is important for a good end-user experience, while at the same time still requiring an hourly refresh so that access can be terminated in an hour or less.
Flows
So what’s all this stuff about flows? We’ll as I mentioned above, my explanation wasn’t fully accurate because depending on the scenario, the process is somewhat different. These different scenarios are called flows. There are several but the main ones that we come across are:
- implicit flow: this one is very common for web based applications such as those written in Angular and React.
- authorization code flow: typically used by installed applications such as mobile apps, as well as app on your desktop. With newer libraries this flow is also more and more being used by the single page apps (Angular and React) because it has some security benefits.
- client credential flow: this is quite a simple flow and typically used for server to server communications.
The differences between the flow related to whether or not there are pre-shared secrets, whether refresh tokens are used, etc. For now, I’ll skip going into too much detail.
There are extensive explanations given here: https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-protocols
a walk through
For all of this to make sense, it helps to see this in action. It really does.