Jarede
Jarede

Reputation: 3498

Azure AD only generates invalid Access Token to use with Microsoft Graph

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:

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();
        });
    }
}

Upvotes: 0

Views: 1310

Answers (1)

Allen Wu
Allen Wu

Reputation: 16498

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.

Upvotes: 1

Related Questions