Getting Access Token for Microsoft Graph Using OAuth REST API, Part 3
Ready to finish implement access tokens in that OAuth API that's calling a REST API? Read on to get started (and to finish)!
Join the DZone community and get the full member experience.
Join For FreeWelcome to the third and final post of this series! Here's where you can Part 1 and Part 2 for reference.
Flow 3 - Get Access Token From Refresh Token (Refresh Token Grant)
Access tokens eventually expire; however, some grants respond with a refresh token which enables the client to get a new access token without requiring the user to be redirected.
Getting an Access Token from the Refresh Token is a simple process, all we need to do is to send the following request:
grant_type
: The grant flow we want to use,refresh_token
in this case.client_id
: The Client ID (Application ID) of the application we created in the previous step.client_secret
: The Client Secret we created in the previous step.resource
: The name of the resource we would like to get access,https://graph.microsoft.com
in this case.refresh_token
: The Refresh Token.
We will receive a response with a JSON object containing the following properties:
token_type
: The valueBearer
expires_on
: The token expire timestamp in Unix epoch time.access_token
: The access token we needed to access the Graph API.refresh_token
: Refresh Tokens can also expire (although it may take weeks or months). When that happens, a new Refresh Token will be returned here so it can be used as a replacement for the old one.
Those with sharp eyes will notice another small change in the request compared to the others - the URL of authorization endpoint is different. This is to show that we have another option - instead of using the tenant directory we can just use the common endpoint:
POST https://login.microsoftonline.com/common/oauth2/token
This, in turn, will redirect the request to the original tenant directory, so this endpoint can be used interchangeably with the direct endpoint.
The common endpoint will not work for all grant flows like Option 1 (Client credentials Grant). It requires a User context in order to know which directory the app sits in.
Option 2, Resource Owner Credentials Grant, allowed us to get a "delegated token" (token with both Client and User) using the User credentials. But, in many cases, we wouldn't have access to the user password - this flow is more designed for System Accounts, where we have full control of the user.
In most cases, instead of having the user credentials, we will have another Access Token, only issued to our application instead of the desired API. To better understand that, we have to dive a little deeper into the Access tokens themselves.
Understanding the Access Token
So what is that access tokens are made of anyways? Well, the token is actually a JSON Web Token - a signed JSON document, passed in base 64 format (so it can be sent in the request header). From a practical standpoint, we can think about it as a text string which contains information.
The token is signed (but not encrypted) which means while we can read it (for example, by using the jwt.io parser) we can't modify it. The first thing Graph API does is to validate the signature, so if the token wasn't generated in a place it trusts (like Azure Active Directory Authorization Services) it will not accept the request.
You can learn about JWT format at jwt.io.
So what does the token contain? Let's use the jwt.io parser and look at a basic access token, the one returned from the Client Credentials Grant Flow (option 1):
{
"aud": "https://graph.microsoft.com",
"iss": "https://sts.windows.net/f62479de-8353-4507-aaf3-6a52320f641c/",
"iat": 1521555934,
"nbf": 1521555934,
"exp": 1521559834,
"app_displayname": "MicrosoftGraphClient",
"appid": "2024c60c-fe49-4ca0-80e8-94132f56d7c4",
"roles": [
"Directory.Read.All",
"User.Read.All",
...
"Mail.ReadWrite",
],
"tid": "f62471de-8358-4907-aaf3-6a52320f741c",
}
As we can see, the token contains many pieces of information which are called "claims" in OAuth terms. JWT can contain any number of claims - but there are a couple of common ones. Let's review some of those claims:
app_displayname
: The Application Name.appid
: The Application ID (Client ID).- Several dates related claims like
iat
(Issued At),exp
(Expire) andnbf
(Not Before) which are used during token validation. iss
: The Issuer of the token, which is a combination of constanthttps://sts.windows.net/
and our Tenant ID.tid
: Our tenant active directory ID.
There are also 'aud' and 'roles' claims which are very important, but, before explaining those, let's see what is different between this token and an access token which also contains a user.
Delegated Access Token
As we discussed on Option 2 (Resource Owner Credentials Grant), access tokens can also contain a user inside. Let's look at delegated access token:
{
"aud": "https://graph.microsoft.com",
"iss": "https://sts.windows.net/f62479de-8353-4507-aaf3-6a52320f641c/",
"iat": 1521565239,
"nbf": 1521565239,
"exp": 1521569139,
"app_displayname": "MicrosoftGraphClient",
"appid": "2024c60c-fe49-4ca0-80e8-94132f56d7c4",
"family_name": "Kent",
"given_name": "Clark",
"name": Clark Kent",
"unique_name": "clark.kent@supercorp.onmicrosoft.com",
...
"tid": "f62472de-8358-4507-aaf3-6a52320f641c",
}
As we can see, most of the claims are the same of the previous example, except a couple of additional claims regarding the user:
given_name
,family_name
,name
: The username.unique_name
: The user's full unique moniker, including the company AAD name.
This is what a delegated token looks like - it contains both Client and User claims.
It's time now to look at the aud
and roles
claims.
Token Audience
The aud
claims contain the token audience - who this access token is issued for.
The audience of the token is a very important security principle in OAuth: access tokens are issued for a specific purpose, which means there is only one place they can be used. In our example, the token audience is the Graph API URL, which means this token is only valid for that service (the Graph service checks that the audience
is https://graph.microsoft.com
, otherwise, an exception will be thrown). This makes sure that access tokens can only be used for the purpose for which they were issued.
So how do we issue access tokens for different audiences? This is controlled by the resource
parameter which we stated in our request. Since we passed https://graph.microsoft.com
we received a token with https://graph.microsoft.com
as the audience. If we wanted to use the token for another Microsoft service like Azure Graph (the previous version of Microsoft Graph) we would have used https://graph.windows.net
as the resource value. Most of Microsoft's REST APIs can be accessible if we issue a correct access token for them, for example, in order to use the SharePoint REST API we need to pass the SP Site URL in the resource (https://<tenant-name>.sharepoint.com
).
Besides issuing access tokens for Microsoft services, at any time we can issue tokens against our own apps.
In all previous examples, we issued tokens for a specific target - the Microsoft Graph API. But if we wanted a delegated token (so we can perform operations on behalf of a user) we would need the user credentials. In many cases, we won't have those - instead, we will have another access token - an access token which was issued for our application:
{
"aud": "2024c60c-fe49-4ca0-80e8-94132f56d7c4",
..
Tokens which were issued for a specific app have their own aud
claim that contains that app's ID (Client ID). They are used to prove that the user contained in the token is authenticated for that application.
App-specific tokens usually come from two additional OAuth flows (which are out of the scope of this post) - the Authorization Code Grant and Implicit Grant. When we have those tokens, and we want to use another Microsoft service on behalf of the authenticated user, we can't just use them because their aud
claim is "wrong" (it contains our app ID instead of the desired API, for example, https://graph.microsoft.com
).
In those cases, we need to exchange our application token in a similar one, only addressed for the service we want (and still contains the authenticated user claims). And, for this exact purpose, we have the fourth flow - the "On Behalf of Grant" flow.
For more information about tokens and claims visit: Azure AD token reference.
Scopes
Besides the audience, we have another important claim - the scopes of the resource.
Scopes allow us to have a greater resolution regarding access to resources, for example, to separate between a read/write access or to specify which methods inside the service are allowed.
Every resource can define its own scopes - it's entirely up to the service creators. Inside the access token, scopes are just arrays of strings, so they can contain anything. When using a resource, it is important to know what the valid scopes are so we can issue them accordingly.
Note: Some APIs may refer to scopes as roles (like in the above example). In the OAuth context, they are the same thing.
Microsoft Graph scopes are directly related to the permissions we setup while creating the app - every permission equals to a specific scope. For example:
Calendars.Read
: Read user calendars.User.Read.All
: Read all users' full profiles.
This means that our access token scopes are the direct result of the permissions we set up in the first place - the more permissions we asked for, the more scopes we have. When using any Graph method, it is important to make sure that the access token contains the required scopes.
More information:
Now that we have our basics covered, it's time to finally discuss the last grant flow, the On Behalf of Grant flow.
Flow 4 - Get an Access Token From Another Access Token (On Behalf of Grant)
Flow 4 allows us to convert an access token which was issued for our app into another access token which carries the user claims but is addressed to another app/service (delegated access token).
For this flow. we need to send the following POST message:
POST https://login.microsoftonline.com/{{AAD_name}}/oauth2/token
grant_type
: The grant flow we want to use,urn:ietf:params:oauth:grant-type:jwt-bearer
in this case.client_id
: The Client ID (Application ID) of the application we created in the previous step.client_secret
: The Client Secret we created in the previous step.resource
: The name of the resource to which we would like to get access,https://graph.microsoft.com
in this case.requested_token_use
:on_behalf_of
assertion
: Our currentaccess_token
The new access token we get in the response will contain almost the exact information the current one has, only the audience will be different (https://graph.microsoft.com
in this case) which will allow us to perform delegated operations against the Graph API.
To learn more about this flow, see: Azure Active Directory v2.0 and OAuth 2.0 On-Behalf-Of flow
Conclusion
Getting an access token wasn't easy and required some preparation, but once we have it all we need to do is to send it in the request Authorization header in order to gain access to the Graph API. There are a couple of points we need to keep in mind though:
- The access token can only do what it can do. Meaning that it is up to us to make sure our access token contains the correct audience, scopes, client and user claims necessary for the specific method we want to use.
- Access tokens can only be addressed to one audience (resource), which means that, in many cases, we will need to use several access tokens - each one addressed for a different service.
- Access tokens are usually meant for short-term use (access tokens issued from AAD will expire in one hour). Make sure your application can handle the token expiry and utilize the refresh token to get a new access token.
- Refresh tokens can also expire, so always plan for that scenario.
- Client Secret and Refresh Tokens are sensitive information and should only be kept in trusted client applications (for example server-side or a secured storage). They are never to be kept in browsers, this is what the other two flows are for (Authorization Code Grant for websites and Implicit Grant for SPAs).
You can learn more about AAD authentication scenarios or Authorization Grant Flows at these links.
Published at DZone with permission of Eran Hertz. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments