To make sense of this section, you need to already know the basic concept behind OAuth. So I won’t repeat what OAuth is and what scenarios it covers. Instead I will touch on some important topics to keep in mind (and commonly causes confusion to new identity developers) while working with an Authorization Server such as Azure AD.
As you probably know, OAuth spec/framework defines four flows:
- Those that cover user interaction scenarios (i.e. a user supplies credentials in a UI and gets the consent screen and manually approves authorization):
- Authorization Code Flow – when the client is a web application and the user is using a browser agent
- Implicit Flow – when the client is a mobile app or a client app running in a browser
- Those that cover scenarios without user interaction (i.e. credentials are supplied directly via the application code):
- Resource Owner Password Credential Flow – the user credentials are already supplied to the application. This is similar to what WS-Trust in the SOAP/enterprise world
- Client Credentials Flow – similar to the Resource Owner flow, with the main semantic difference being that the credentials supplied by code are client credentials, rather than the user (i.e. resource owner credentials)
Now the thing about OAuth is that it is an authorization framework, not authentication one. Consider the Authorization Code Flow for example. When a user enters his credentials to login to an Authorization Server, what he gets back eventually is an access token (skipping the detail of the authorization code/access token exchange process) which is then supplied to the client application. The problem is that many applications assume that since the user has supplied credentials to the Authorization Server, then he is authenticated. But this is not true. The user has indeed authenticated to the Authorization Server, but the access token provides no information about who the user is; it just contains information about what resources the user grants the application access to. In other words, the access token is for resource access, not for the application to identify who the user is.
What many providers started doing is implementing custom mechanisms to use OAuth for authentication. For example they would come out with a custom defined scope – “authenticate” – for example:
Therefore the Authorization Server would know that this is an authentication request and would reply back with the access token, that the application can then exchange with user profile information by invoking a custom userinfo endpoint.
This approach led to two problems:
- First, the solution was not a standard; meaning two different identity providers would create two different implementations making them non-interoperable – which defeats the whole purpose of having a standard protocol at the first place
- Second, this solution opened the door for security breaches as malicious applications might steal the access token and impersonate the user (this requires a detailed explanation which is beyond our scope; for more info search about Facebook OAuth security breach for an example)
Given this, the industry came up with a protocol called OpenID Connect which builds on top of OAuth and adds (among other things) authentication capabilities on top of OAuth.
So now – assuming the Authorization Code Flow – the code request would look something like the follows:
GET /authorize?scope=openid profile&response_type=code…
Notice here the presence of scoped openid and profile. These are defined as part of the OpenID Connect specification. Scope openid means it’s an OpenID Connect request, and profile means we’re asking access to the user profile info.
From here the flow goes the same: the user supplies the credentials in a UI, accepts the consent screen, and gets back an authorization code. The application then sends the authorization code to the Authorization Server for exchange of the token. Except now, what the application gets back is something like the follows:
This time we not only have the OAuth access token (which again is the authorization token just like before), but now there is also an id_token, and this token identifies (or helps identify) the user to the application. If the information in the id_token is not enough for the application to authenticate (and for example personalize the experience for) the user, then it contact a standard userinfo endpoint exposed by the Authorization Server supplying the id_token and it gets back the user profile information.
This way OpenID Connect has utilized OAuth and its supported flow, but added two core features:
- The support of an id_token which identifies the user, and unlike the access_token which is meant to authorize resource access, is actually meant for the application to authenticate the user
- Exposing a userinfo endpoint where applications can supply the id_token in exchange of user profile information – which for example can be used by the application to personalize the user experience and provide authorization control
OpenID Connect also adds other features, most importantly session management; meaning the ability to provide signin and signout functionalities via cookies – remember we’re talking authentication now!
Repetition is key, so let me summarize this one final time:
- The access_token of the OAuth is not meant to authenticate users to client applications. It contains no information about who the user is, and it’s just telling the application “hey, you have access to these set of resources on behalf of a certain user”. Therefore the client application has no idea who the user is, it only knows that this user authorized it to access his/her resources.
- The id_token added by the OpenID Connect specification, identifies the user itself. Therefore the client application can use this token to authenticate the user, either by using the information inside the token if they are enough, or by invoking a userinfo endpoint and supplying this token