MS Graph / OAuth 2.0 Integration - Best practice questions and issues

Tom Sienkiewicz
Mega Sage

Hi, I really hope someone who has had some success properly implementing the Authorization code flow for MS Graph API can point me in the right direction as I feel I am missing some bits to fully understand the integration.

I have built my integration with MS Graph and it is working fine. I am requesting Access tokens, getting those and successfully using the Graph API to interact with e.g. Calendars or Mail. So you might ask, what's wrong?

Well, it appears to be running fine but I feel it is still not perfect. It really depraves me of valuable sleep. So, let me break down the issues I feel I have:

1. In the Application Registry, I have added my application and then the OAuth Entity Profile, with the Grant Type "client_credentials" and the Scope "https://graph.microsoft.com/.default":

find_real_file.pngfind_real_file.png

So why is ServiceNow forcing me to add the Grant type and Scope again when constructing my token request??? This works:

var tokenRequest = new sn_auth.GlideOAuthClientRequest();
tokenRequest.setGrantType('client_credentials');
tokenRequest.setScope('https://graph.microsoft.com/.default');

var oAuthClient = new sn_auth.GlideOAuthClient();
var tokenResponse = oAuthClient.requestTokenByRequest("Azure AD Token", tokenRequest);

BUT THIS DOESN'T and gives an error:

var tokenRequest = new sn_auth.GlideOAuthClientRequest();

var oAuthClient = new sn_auth.GlideOAuthClient();
var tokenResponse = oAuthClient.requestTokenByRequest("Azure AD Token", tokenRequest);

OAuthProblemException{error='invalid_request', description='AADSTS900144: The request body must contain the following parameter: 'grant_type'.

OAuthProblemException{error='invalid_request', description='AADSTS90014: The required field 
'scope' is missing from the credential. Ensure that you have all the necessary parameters for
the login request.

So, what's the point of adding all that to my Application Registry?
Or am I missing something here/using the wrong API?

2. Obviously, using "client_credentials", we cannot utilize the Refresh Token. The question is, is "Authorization Code" approach generally more recommended?
If yes, why? Can this approach work with "Application" permissions, or only with "Delegated" permissions? As I understand, Application > Delegated, right?

So, I created another Application Registry profile, this time with "Authorization Code" grant type, set everything up, created OAuth 2.0 Credential,
clicked the "Get OAuth token", got the pop-up where I had to click "Accept" - perfect. There is a new token in the Tokens table:
find_real_file.png
Now I get lost a bit and here are my questions:
- The pop-up with request for authorization appears only at the set-up and not every time the integration is fired, for every user, right? Sorry if that's a noob question.
It seems it would be really annoying if ServiceNow asked every user to click "Accept" before it can check their calendar. Then again, it seems to work based on impersonation...
- I understand that you only have to click the "Get OAuth token" link once and then the Refresh token should get a new Access token before it expires right?
In my case, the Access token expired after an hour and nothing else happened. I don't even know if I received the Refresh token - seems I did not. Also, why is the token field empty?
- how do I get the Refresh/Access tokens to run fully auto in the background? Is it not provided OOTB? Is there a specific API I have to use?
Do I need to write my own logic to get the Refresh Token, monitor the Token table to see if any Access token is active and if not, manually trigger
the generation of new Access token with Refresh token?
- What is the recommended way to trigger the REST service? Create a record in Outbound REST and link

it to the credentials? Or just trigger REST from script and build the whole Request header/body with authorization there?

Many thanks for any pointers in the right direction. It is hard to find valuable examples online, everyone seems to be using the "client_credentials" approach, I have not seen an example of setting up the Authorization Code end to end.
So I really hope someone can shed some light here. I even tried to install and run the Integration Hub spoke for "Exchange Online" as they use this approach there,
but could not see how it handles Refresh tokens...

Sorry for the long post but hopefully everyone can benefit from the answers/discussion 🙂 thanks!

 

17 REPLIES 17

Hi, are you using Client Credentials or Auth Code?

Can you describe what you have set up so far? How are you logging the token? The token record should be sitting in the OAuth Credentials table (or two tokens if you use auth code).

I'm using the same setup client_credentials 

I added a log in the script include OAuthUtil to see the token but when I test it in Postman I see the error missing audience meaning the token is not correct, so I run a script with the following

var oAuthClient = new sn_auth.GlideOAuthClient();
var params = {grant_type:"client_credentials",resource:"https://graph.microsoft.com/"};
var tokenResponse = oAuthClient.requestToken('{{name of application registry}}',global.JSON.stringify(params));
var token = tokenResponse.getToken();

 

with the script I get the correct token but I don't want to request a token each time I trigger the API that not the point of tokens

OK... actually, you should not need to use new sn_auth.GlideOAuthClient(); at all. You should not use any script except for firing your Rest messages.

Heres what I would suggest to check/do:

1. Create Application Registry profile and make sure it is configured properly (endpoints for token request/redirection etc.)

2. Create Scope(s) - this depends on what you are trying to get access to e.g. Calendars.Write

3. Assign those defined scopes under OAuth Entity profile record.

4. Create OAuth credential and select your entity profile in it.

5. Create Outbound Rest message - here select OAuth 2.0 Authentication. In the respective methods, select for authentication 'inherit from parent'. Construct your messages according to the specific API/endpoint. Make sure you add Content-Type application/json to every method.

6. Fire your methods e.g. var request = new sn_ws.RESTMessageV2('Your Message','Your Method'); Construct the Body as you need but DO NOT add any authentication/other headers to your message.

7. As you fire the message, you will see a token being added to the OAuth Credentials table (under Manage Tokens menu in navigator).

8. ServiceNow checks if the token is active/expired and updates the token once it has expired.

So unless your external provider has some VERY specific and unusual setup you should not need to write a single line of code except for triggering REST messages.

Thank you for the reply

This what I used as first sight but it did not work as I was getting the error '

ccess token validation failure. Invalid audience' 

after decoding the token I saw that the resource param is empty meaning the POST Request to get the token is not properly set

What API are you trying to integrate with?

Are you building POST manually to request an access token from the external provider (this is not what you should be doing in 90% of cases)? ServiceNow handles that automatically if you follow above steps.

There is also a possibility to write custom OAUth API script if you have a very unusual case but I'm not sure if it is documented.