2

I am using 'passport-azure-ad-oauth2' npm module, to get an access token, which I could then pass to the MS Graph API.

passport.use(new AzureAdOAuth2Strategy({
    clientID: process.env.OUTLOOK_CLIENT_ID,
    clientSecret: process.env.OUTLOOK_SECRET,
    callbackURL: '/auth/outlook/callback',
},
    function (accesstoken: any, refresh_token: any, params: any, profile, done) {
        logger.info('Completed azure sign in for : ' + JSON.stringify(profile));
        logger.info('Parameters returned: ' + JSON.stringify(params));
        const decodedIdToken: any = jwt.decode(params.id_token);
        logger.info('Outlook Access Token:' + accesstoken);
        logger.info('Decoded Token: ' + JSON.stringify(decodedIdToken, null, 2));

        process.env['OUTLOOK_ACCESS_TOKEN'] = accesstoken;
        // add new user with token or update user's token here, in the database

    }));

And then, using '@microsoft/microsoft-graph-client' npm module, to fetch Calendar events from the Graph API as follows:

try {
    const client = this.getAuthenticatedClient(process.env['OUTLOOK_ACCESS_TOKEN']);
    const resultSet = await client
                .api('users/' + userId + '/calendar/events')
                .select('subject,organizer,start,end')
                .get();
    logger.info(JSON.stringify(resultSet, null, 2));
} catch (err) {
    logger.error(err);
}

getAuthenticatedClient(accessToken) {
    logger.info('Using accestoken for initialising Graph Client: ' + accessToken);
    const client = Client.init({
        // Use the provided access token to authenticate requests
        authProvider: (done) => {
            done(null, accessToken);
        }
    });

    return client;
}

However, however, using the accessToken provided on Successful Login, I get the following error : CompactToken parsing failed with error code: 80049217

Any suggestions what am I doing incorrectly ???

UPDATE : These are the scope I am using : 'openid,profile,offline_access,calendars.read'

UPDATE : After editing the scopes a bit, now I am getting the following error : Invalid Audience.

On decoding the token received at jwt.ms, this is the value for 'aud': "00000002-0000-0000-c000-000000000000"

Is it the case that passport-azure-ad-oauth2 is the wrong library for retrieving tokens for MS Graph API ?

vasu014
  • 187
  • 1
  • 3
  • 12
  • According to my error message, could you please catch ```process.env['OUTLOOK_ACCESS_TOKEN'] = accesstoken;``` to print your access token? – Jim Xu Nov 05 '19 at 01:06
  • Turns out, **passport-azure-ad-oauth2** gives access token for the old Azure AD Graph API. So I switched to **passport-microsoft** which retrieves tokens for the new Microsoft Graph API – vasu014 Nov 05 '19 at 05:57

6 Answers6

2

Turns out there is a passport library for microsoft-graph api : passport-microsoft

I used MicrosoftStrategy from that package and everything seems to be working fine.

passport-azure-ad-oauth2 is for the old Azure AD Graph API, while passport-microsoft is for the new Microsoft Graph API

vasu014
  • 187
  • 1
  • 3
  • 12
1

According to my test, we can use the following code to get the access token. app.js

require('dotenv').config();
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var session = require('express-session');
var flash = require('connect-flash');
var passport = require('passport');
var OIDCStrategy = require('passport-azure-ad').OIDCStrategy;


// Configure simple-oauth2
const oauth2 = require('simple-oauth2').create({
  client: {
    id: process.env.OAUTH_APP_ID,
    secret: process.env.OAUTH_APP_PASSWORD
  },
  auth: {
    tokenHost: process.env.OAUTH_AUTHORITY,
    authorizePath: process.env.OAUTH_AUTHORIZE_ENDPOINT,
    tokenPath: process.env.OAUTH_TOKEN_ENDPOINT
  }
});
var users = {};

// Passport calls serializeUser and deserializeUser to
// manage users
passport.serializeUser(function(user, done) {
  // Use the OID property of the user as a key
  users[user.profile.oid] = user;
  done (null, user.profile.oid);
});

passport.deserializeUser(function(id, done) {
  done(null, users[id]);
});

// Callback function called once the sign-in is complete
// and an access token has been obtained
async function signInComplete(iss, sub, profile, accessToken, refreshToken, params, done) {
  if (!profile.oid) {
    return done(new Error("No OID found in user profile."), null);
  }


  // Create a simple-oauth2 token from raw tokens
  let oauthToken = oauth2.accessToken.create(params);

  // Save the profile and tokens in user storage
  users[profile.oid] = { profile, oauthToken };
  return done(null, users[profile.oid]);
}

// Configure OIDC strategy
passport.use(new OIDCStrategy(
  {
    identityMetadata: `${process.env.OAUTH_AUTHORITY}${process.env.OAUTH_ID_METADATA}`,
    clientID: process.env.OAUTH_APP_ID,
    responseType: 'code id_token',
    responseMode: 'form_post',
    redirectUrl: process.env.OAUTH_REDIRECT_URI,
    allowHttpForRedirectUrl: true,
    clientSecret: process.env.OAUTH_APP_PASSWORD,
    validateIssuer: false,
    passReqToCallback: false,
    scope: process.env.OAUTH_SCOPES.split(' ')
  },
  signInComplete
));

For more details, please refer to the document and the sample.

Jim Xu
  • 21,610
  • 2
  • 19
  • 39
  • I tried this, however OIDCStrategy requires sessions. We are already using jwt. Is there a way to use this strategy without sessions?? – vasu014 Nov 04 '19 at 03:05
  • What do you mean? Is that you want to use AzureAdOAuth2Strategy? – Jim Xu Nov 04 '19 at 03:25
  • In the product that I'm working on. We use google oauth2 using the passport module to login and get an access token, which is used to fetch user's calendar events. Now I'm trying to integrate office 365 in the same. However, we've used jwt for storing auth data, so don't think OIDCStrategy will fit here because it uses sessions. I need a strategy which can be used with jwt – vasu014 Nov 04 '19 at 03:38
1

I had the same issue. In my case, the Authorization header was malformed:

Bearereyxxxx

rather than

Bearer eyxxxx

I'm new on that project, but it seems it used to be accepted by Azure beforehand.

rcomblen
  • 4,579
  • 1
  • 27
  • 32
0

I solved this passing just the accessToken in the authenticationProvider instead of old "Bearer " + token: eg:

TokenCredential tokenCredential = tokenRequestContext -> Mono.just(new AccessToken(accessToken, OffsetDateTime.MAX));
IAuthenticationProvider authenticationProvider = new TokenCredentialAuthProvider(tokenCredential);
0

I got this error with the Graph API because I was adding multiple authorization headers as I was paging through results. Each @odata.nextlink page was adding the same token causing it to break after the initial request with this error message.

Here's my code in C#:

private Tuple<JToken, JToken> GetUsersAndNextLink()
    {
        client.BaseUrl = new Uri("https://graph.microsoft.com/v1.0/users");
        var request = new RestRequest("", Method.GET);
        client.AddDefaultHeader("Authorization", string.Format("Bearer {0}", Token));
        IRestResponse response = client.Execute(request);
        var content = response.Content;
        var des = JsonConvert.DeserializeObject<JObject>(content);
        var nextlink = des["@odata.nextLink"];

        var value = des["value"];
        return new Tuple<JToken, JToken>(value, nextlink);
    }

    private Tuple<JToken, JToken> GetUsersAndNextLink(string prevnextlink)
    {
        client.BaseUrl = new Uri(prevnextlink);
        var request = new RestRequest("", Method.GET);
        client.AddDefaultHeader("Authorization", string.Format("Bearer {0}", Token));
        IRestResponse response = client.Execute(request);
        var content = response.Content;
        var des = JsonConvert.DeserializeObject<JObject>(content);
        var nextlink = des["@odata.nextLink"];
        var value = des["value"];
        return new Tuple<JToken, JToken>(value, nextlink);
    }

The code iterates each time the @odata.nextlink value is present in the response to page through the results. However, adding multiple headers causes an error every time.

Removing the third line of GetUsersAndNextLink(string prevnextlink) method call fixed this issue for me.

Make sure your authorization headers are in order - this seems to be a catch all error code for these issues when working with the Graph API.

Eric Conklin
  • 539
  • 4
  • 17
0

The accepted answer only helps people using the specific passport-azure-ad-oauth2 npm module, which was the OP's case, but the error is a generic to multiple situations when working with Microsoft Graph.

For instance, one can get this error if one tries to connect to Graph by, incorrectly, passing the refresh token in the Authorization header instead of a valid access token:

GET https://graph.microsoft.com/v1.0/me
Authorization: Bearer incorrectlySuppliedRefreshTokenHere

Some may have tried this because their access token had expired, hoping the refresh token will work.


Solution

To get a valid authentication token using the refresh token, you need to make an http POST request to

https://login.microsoftonline.com/{yourTenantId}/oauth2/v2.0/tokenrequest

with (1) Authorization Header, like this:

Authorization: Bearer {yourExpiredAccessToken}

and (2) Content-Type header like this:

Content-Type: application/x-www-form-urlencoded

and (3) with body containing key-value pairs (where the values are urlencoded) that look like this (exclude curly braces and substitute variables inside curly braces with actual values)...

client_id={yourClientId}&grant_type=refresh_token&scope=offline_access%20https%3A%2F%2Fgraph.microsoft.com%2FUser.Read&client_secret={yourClientSecret}&refresh_token={yourRefreshToken}

Note the value for scope is just an example; you should use the same scope you used when you originally requested the access token and refresh token.

Submit the request and you will get back a json response that contains a valid access token which you can then use in your Authentication Bearer header and you will be able to connect successfully.

Also note you will need to catch a

ServiceException ("code":"InvalidAuthenticationToken", "message":"Access token has expired or is not yet valid.")

any time you make a request when the access token has expired. Handle it by resubmiting the POST with grant_type=refresh_token, and again update your header with the new auth token and retry.

ΩmegaMan
  • 29,542
  • 12
  • 100
  • 122
Search4Sound
  • 188
  • 2
  • 5