If you have gone through the typical authentication scenarios, then you’ve probably seen that almost every single one is calling the Graph API. Where that is already cool as such, we’re all probably more interesting in using our own APIs… Today’s post will go through the process of calling -both- the Microsoft Graph API and your own API from the same code base.
Starting Knowledge Assumption
My assumption is that you are already familiar with the basics of Oauth, where you’re aware that a Single Page Application (SPA) is using an “Implicit Grant Flow“. Also be aware that the Azure Active Directory (AAD) v1 endpoint differs from the v2 endpoint in terms of resources & scopes.
In the v1 endpoint, you would target a “resource” in order to get authorization ;
Where the v2 endpoint rotates around the usage of scopes ;
The latter indicates both the resource & the permission that is targeted…
Setup of the day
As our test bed for today, we’re having a Single Page Application that has its own “Application ID” (clientid in Oauth). Which will be linked to two backend APIs; Microsoft’s Graph API and our own
Both our APIs have a given set of scopes that indicate the permissions that a user grants towards these APIs…
How does the above look from an Azure Active Directory (AAD) perspective?
We’re having our application registration, which is backed by the two APIs. And they both have a given set of “Permissions” (=> scopes) configured… In addition, if you go to the details of the scopes, you’ll see that these have a unique identifier too. Why is this important to note? Imagine if “vmsnoozerapi” would also have an “email”-permission. How would we be able to address this in our authorization request? … As you’ve guessed, it’s best to use the “fully qualified scope name” (if the term even exists? 😉 ) to refer to the scope.
Now let’s take another look at our Implicit Flow… So the SPA uses its own clientid to redirect the user to AAD.
Here it’ll ask for the user to login, where a (set of) scope(s) is immediately provided in this request. At this time, a consent interruption might arise, to provide the user the ability to agree with the using the application. This is used a safeguard from having applications gain more permission than a user is aware of…
If the consent and login succeeds, then the SPA is provided with an Access Token. In turn, this token can then be used to access the “resource” (~”API”) we requested authorization for, within the “scope” of the permissions (~scopes) that were requested.
If you want to see this more tangible? Check out the network calls from the developer mode of your browser… You’ll notice that the API calls will have a header called “authorization” which has a value staring with “Bearer ey”.
You can copy the “ey…”-value, and then paste it into https://jwt.ms/ to see the content of that token!
Making it real…
We’re going to do two API calls from the same procedure… The first one will get the user’s profile by leveraging the Graph API. Where the second one will call our own API.
What is behind this “callAPI” function?
At first we’ll be getting our API token. If possible, we’ll be doing it in the background.
(Note ; In the following screenshot, I’ve added an actual value (of our scope) in order to make it more tangible…)
Once we’ve obtained our access token, we’ll be injecting it into our API call as the way to authenticate ourselves.
If we run this part, then we’ll first go through a login process. This was not visible in the piece of code I showed you with the two “callAPI” invocations. Though it was handled elsewhere, where you can see that the scopes for the graph API were added. Why is this? An access token cannot span multiple resources. So when we’re going to do the call to our APIs, the library will use separate tokens for each API that we’re calling.
So one we run the two function, we’ll see that first an an access token is requested for the graph API.
And next up, one for our own API.
If we’re going to decode the access token (which are formatted as JWT tokens)
Then we can see that the “aud” (audience = resource identifier) of the graph access token is referencing the graph API.
Where our own API is referenced too.
This is important to note! When you’re call APIs, always request an access token for that specific API. In addition, if you’re building an API that leverages an Oauth (or OpenID Connect) flow. Then be sure to validate your tokens accordingly!
As you can see from the dirty code snippet… There are a lot of things to validate when using tokens. Do not cheap out on any of those, as it will greatly reduce the security level of your API. For instance, if you do not check the “Audience”, then an access token of another API (f.e. the graph api) could be used to target your API. Likewise for the issuer… This is the party you are trusting. If you do not validate this, then an attacker could issue their own access tokens for your API. That does not seem right, does it? Also check the singing keys, as this is basically proving that the issuer is really the issuer. Again, otherwise an attacker could impose as the issuer you are validating. Where, last but not least, also check the lifetime. Otherwise an attacker could keep on using an access token that was obtained at an earlier time.
In my opinion, OpenID Connect and Oauth2) are the way forward for authentication in cloud native applications. The flows have been carefully deviced and provide robustness. As mentioned in an earlier post, like with anything security related, there is a learning curve to comprehend it all. Here I hope that this post has provided some additional insight in how you can secure your applications. I tried to make it tangible, where I’m fully aware that at times this might blow a fuse for some.
If nothing else, I hope that this post provided the insights about these to things ;
- Do not cheap out on token validation for your endpoint!
- Request an access token per resource to which you want access.
As always, feedback is appreciated! 😉