0
votes

I've setup an App Registration in Azure Active Directory so that I can access Microsoft OneNote notebooks/sections/pages via the Microsoft Graph.

It's allowing pretty much every Microsoft holding account to login, so I'm using https://login.microsoftonline.com/common/oauth2/v2.0/authorize as my authorisation endpoint and https://login.microsoftonline.com/common/oauth2/v2.0/token as my token endpoint. Within the App Registration, I've set API permissions as:

  • Notes.Read.All
  • offline_access
  • openid
  • User.Read

I've added a localhost in my Redirect URIs which is listed as "web", so that i can follow the flow through.

The problem I am getting, is that when I eventually receive my access_token, I receive an error when using it as a Bearer Token against: graph.microsoft.com/v1.0/me/onenote/notebooks

{
    "error": {
        "code": "40001",
        "message": "The request does not contain a valid authentication token. Detailed error information: {0}",
        "innerError": {
            "date": "2020-10-07T20:37:37",
            "request-id": "c8b0c20e-d096-4fcb-9e97-841b1626537c",
            "client-request-id": "a-uuid"
        }
    }
}

So following the flow through, I have something like this:

  1. Authenticate via this url

https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=99e1fc5a-bl4h-bl4h-bl4h-l0ng3rbl4h&redirect_uri=http://localhost/myapp&response_type=code&scope=https://graph.microsoft.com/User.Read openid offline_access https://graph.microsoft.com/Notes.Read.All&state=abc&response_mode=query

I'm a little unsure if i'm using the correct scope variables here. I want to be able to access all of a users OneNote notebooks/sections/pages and read their profile and do things offline,

  1. When my user successfully authenticates, the code gets redirected here:

http://localhost/myapp?code=M.R3_BL2.15c2b73a-486c-c0f6-95c1-8432603aa7a4&state=abc

  1. I extract the code from the querystring and make a POST like:
curl --location --request POST 'https://login.microsoftonline.com/common/oauth2/v2.0/token' \
--form 'code=M.R3_BL2.15c2b73a-486c-c0f6-95c1-8432603aa7a4' \
--form 'grant_type=authorization_code' \
--form 'client_id=99e1fc5a-bl4h-bl4h-bl4h-l0ng3rbl4h' \
--form 'scope=https://graph.microsoft.com/User.Read openid offline_access https://graph.microsoft.com/Notes.Read.All' \
--form 'client_secret=shhhhASecret'

Now this will return me an access_token in some JSON.

{
    "token_type": "Bearer",
    "scope": "https://graph.microsoft.com/User.Read openid https://graph.microsoft.com/Notes.Read.All",
    "expires_in": 3600,
    "ext_expires_in": 3600,
    "access_token": "EwCQA8l6BAAUO9chh8cJscQLmU+longstring",
    "refresh_token": "M.R3_BL2.CecNbvRse*longstring",
    "id_token": "eyJ0eXAiOiJKlongstring"
}

However, as stated, this access token doesn't seem to give me access to Microsoft Graph. I've tried different variations on scopes to use in my requests, but none seem to generate the correct access token.

The code I use for calling Microsoft Graph from node.js

const http = require('https');

class OneNote {
constructor(bearer) {
        this.bearer = bearer;
        this.HTTP_CODES = {
            OK: 200,
            Unauthorized: 401
        };
    }

async getNotebooks() {
        const options = this._getOptions();
        options.path += 'notebooks';
        return this._requestData(options)
            .catch((err) => {
                throw err;
            });
    }

_getOptions() {
        return {
            method: 'GET',
            hostname: 'graph.microsoft.com',
            path: '/v1.0/me/onenote/',
            headers: {
                'Authorization': `Bearer ${this.bearer}`
            }
        }
    }

    async _requestData(options) {
        console.log(options)
        return new Promise((resolve, reject) => {
            const req = http.request(options, (res) => {
                let data = '';
                res.on('data', (d) => {
                    data += d;
                });
                if (res.statusCode === this.HTTP_CODES.Unauthorized) {
                    res.on('end', () => {
                        reject(data);
                    });
                } else {
                    res.on('end', () => {
                        resolve(data);
                    });
                }
            });

            req.on('error', (err) => {
                reject(err);
            });
            req.end();
        });
    }
}
1
Looks like this access token is OK. Please share your request of calling https://graph.microsoft.com/v1.0/me/onenote/notebooks with your access token. And decode your access token in jwt.io to see if it has the correct aud and scope.Allen Wu
May I know if you hide the real code? Or is your code "M.R3_BL2.15c2b73a-486c-c0f6-95c1-8432603aa7a4"? The access token looks different from the common one. What kind of account are you using? O365 account or Microsoft personal account?Allen Wu
@AllenWu I'm using my personal Microsoft account. When I use the Microsoft Graph Explorer and copy the access token into jwt.io, it gives an error and doesn't display any data let alone for the ones i've generated myself? This is my latest real code from authorization: M.R3_BL2.db535519-c869-054d-df35-64369bfb86fb.Jarede

1 Answers

1
votes

You probably use a wrong permission(scope).

Based on Permissions for listing notebooks, required permissions for personal Microsoft account are Notes.Create, Notes.Read, Notes.ReadWrite.

Notes.Read.All is not required here.

So please add Notes.Read delegated permission in the scope to fix this issue.