Welcome to new things

[Technical] [Electronic work] [Gadget] [Game] memo writing

How to get and update your Microsoft Graph (Office365) API token

One way to manipulate the Web version of Office365 is through the Microsoft Graph (Office365) API.

For example, with the Microsoft Graph (Office365) API, you can use the

  • Send email from OutLook
  • Excel Online file editing
  • SharePoint file retrieval

The web version of Office365 can be used to perform various operations on behalf of users, such as

To use the API, it is necessary to obtain an access token using OAuth2.0, but this OAuth2.0 token-related work is quite complicated.

This section describes how to obtain a token for the Microsoft Graph (Office365) API and how to renew an expired token.

Square Needle

Since it was difficult to handle the OAuth2.0 on my own, I decided to use the AzureAD strategy passport-azure-ad in Passport.js, which is created by Microsoft, based on a tutorial from Microsoft.

We also decided to use simple-oauth2 to update the token, based on the tutorial.

background knowledge

Preparation for application registration

This is true not only for the Microsoft Graph (Office365) API, but for OAuth2.0 in general, but in order to obtain a token, you must register the application that will obtain the token with the token issuer in advance.

For the Microsoft Graph (Office365) API, apps register with Azure Active Directory in Azure.

So, you must have an Azure account to register the application, or create one in advance if you do not have one.

There is a free version of Azure Active Directory, and the application can be registered for free, so there is no Azure fee for using it to obtain tokens.

OAuth2.0 flow and information required for apps

Roughly speaking, OAuth2.0 involves the following exchange.

  1. There is a login link in the app that takes the user to the login screen of the site that issues the token
  2. There, the user logs in and agrees to the token grant consent screen.
  3. If you agree, a token issuance request code will be issued and you will be redirected to the original application with this information
  4. The app sends the received token issuance request code to the site that issues the token, and receives the token as a response.

When transitioning from the login link

  • Identify which app.App ID.
  • Prove that the application is the right one.Secret."
  • Where to return to the application from the consent screen.Redirect URL."

information, so those three pieces of information are needed to create the application.

App Registration

Application registration for Microsoft Graph (Office365) API is done through Azure Active Directory in the Azure Portal.

  • [Azure]-[Azure Active Directory]-[Register App]-[+New Registration]

How to get and update your Microsoft Graph (Office365) API token

Set the "Redirect URI" to http://localhost:3000/auth/callback on the registration screen.

How to get and update your Microsoft Graph (Office365) API token

Types of Accounts Supported

Here, you select the type of account to be supported, which is a bit complicated, so I will provide additional explanation.

First, Office365 is available for individuals and for businesses.

With Office365 for enterprises, a dedicated domain is assigned to each company, and the company adds and manages its own Office365 users.

Office365 for individuals, on the other hand, is managed by Microsoft for the domain, and individuals have their accounts added to Microsoft for use. This is what is called a Microsoft account.

The types of accounts to be set up in the application are

  • Single Tenant
  • multi-tenant
  • Multi-tenant + Microsoft account

There are three types of

For example, if a company uses Office365, the Azure Active Directory contains Office365 users managed by the company.

Then, when registering the app in that Azure Active Directory, if the type of account supported by the app is "Single Tenant", You can limit the users that the app can get only to users in that Azure Active Directory (Office365 users).

On the other hand, if the account type is set to "Multi-tenant", beyond the Azure Active Directory users managed by your company, The application will also be able to acquire tokens for Office365 users from other companies.

Also, if you use "Multi-tenant + Microsoft account", as you might have guessed, the application will also allow you to get a token for your personal Microsoft account.

For example, if an individual user of Office365 wants to obtain a token for his/her own use, obtain an Azure account as appropriate, register an app, and then By setting the account type of that app to "Multi-tenant + Microsoft account", you can create an app that can obtain a personal Office365 token.

Note application information

Go to [Overview] of the application and note Application (client) ID.

How to get and update your Microsoft Graph (Office365) API token

Secret Creation

[Go to "Certificates and Secrets" and click on +New Client Secret under "Client Secrets".

How to get and update your Microsoft Graph (Office365) API token

Note that a secret is generated.

How to get and update your Microsoft Graph (Office365) API token

Application Creation

The app is now registered with Azure Active Directory. Next, actually create the app (web app).

In this case, we are using Express.js + Passport.js to create a simple website (web app) that allows users to log in using AzureAD authentication.

And "token obtained when logging in with AzureAD authentication" = "Microsoft Graph (Office365) API token".

Only the program part is described below. All the source code, including templates and how to use them, has been uploaded to this way (direction close to the speaker or towards the speaker).

server.ts

import * as dotenv from 'dotenv';
import * as express from 'express';
import * as session from 'express-session';
import * as path from 'path';
import * as cookieParser from 'cookie-parser';
import * as bodyParser from 'body-parser';
import * as passport from 'passport';
import { OIDCStrategy } from 'passport-azure-ad';
dotenv.config();

// ini
const INI = {
    REDIRECT_URI: 'http://localhost:3000/auth/callback',
    AUTHORITY: 'https://login.microsoftonline.com/common',
    ID_METADATA: '/v2.0/.well-known/openid-configuration',
    AUTHORIZE_ENDPOINT: '/oauth2/v2.0/authorize',
    TOKEN_ENDPOINT: '/oauth2/v2.0/token',
    SCOPES: [
        'profile',
        'offline_access',
        'user.read',
        'Files.Read',
        'Sites.Read.All'
    ]
};

// user database
const user_db: any = {};

// user data -> user identifier
passport.serializeUser((user: any, cb) => {
    cb(null, user.profile.oid);
});

// user identifier -> user data
passport.deserializeUser((id: string, cb) => {
    cb(null, user_db[id]);
});

passport.use(new OIDCStrategy(
    {
        identityMetadata: `${INI.AUTHORITY}${INI.ID_METADATA}`,
        clientID: process.env.CLIENT_ID,
        responseType: 'code',
        responseMode: 'form_post',
        redirectUrl: INI.REDIRECT_URI,
        allowHttpForRedirectUrl: true,
        clientSecret: process.env.CLIENT_SECRET,
        validateIssuer: false,
        scope: INI.SCOPES,
        passReqToCallback: false
    },
    (iss, sub, profile, access_token, refresh_token, done) => {
        try {
            if (profile.oid) {

                // create user data
                const user = {
                    iss,
                    sub,
                    profile,
                    access_token,
                    refresh_token
                };

                // register user data in user database
                // user identifier is profile.oid
                user_db[profile.oid] = user;

                return done(null, user);
            }
            return done(null, false);
        } catch (err) {
            return done(null, err);
        }
    }
));

// express
const app = express();
const PORT_NO = 3000;

// session
app.use(session({
    secret: 'secret_value_here',
    resave: false,
    saveUninitialized: false
}));

// template
app.set('views', path.dirname(__dirname) + '/view');
app.set('view engine', 'ejs');
app.engine('html', require('ejs').renderFile);

// etc
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());

// passport
app.use(passport.initialize());
app.use(passport.session());

// route
app.get('/', (req, res) => {
    res.render('index.html', { user: req.user });
});

app.get('/auth/signin',
    passport.authenticate('azuread-openidconnect', { failureRedirect: '/' })
    , (req, res) => {
        res.redirect('/');
    }
);

app.post('/auth/callback',
    passport.authenticate('azuread-openidconnect', { failureRedirect: '/' })
    , (req, res) => {
        res.redirect('/');
    }
);

app.get('/auth/signout',
    (req, res) => {
        // clear session database
        req.session.destroy(() => { });
        res.redirect('/');
    }
);

app.listen(PORT_NO);

supplementary explanation

  • Authentication part is done by passport-azure-ad.
  • Enter the "App ID", "Secret", and "Redirect URL" that you wrote down in the configuration file .env and use it.
  • The scope of the token can be changed using SCOPES.
  • Once authenticated, the user's OpenID is received, and the token is added to a simple DB data_db as user data using the OpenID as the key.
  • App account type was tested with "Multi-tenant + Microsoft account"

We have written a separate article on how to use Passport.js.

www.ekwbtblog.com

Application Execution

Running the application will launch a local web server and open http://localhost:3000 in a browser.

How to get and update your Microsoft Graph (Office365) API token

Click on signin and you will be taken to the Office365 logon screen. Log on and agree to obtain a token.

How to get and update your Microsoft Graph (Office365) API token

When you return to the application via redirection, you will see the tokens (access token and refresh token) and user information.

How to get and update your Microsoft Graph (Office365) API token

When using the API, the access token is obtained using the refresh token obtained here, and the API is called with that access token.

This is followed by how to use the tokens and how to renew expired tokens.

Renewing expired tokens

The Microsoft Graph (Office365) API token has the following specifications

  • Access token is valid for 1 hour
  • When an access token expires, renew the access token with a refresh token
  • Refresh tokens are valid for 90 days
  • When you renew your access token with a refresh token, a new refresh token is also issued

In other words,Once a refresh token is acquired, it cannot be used permanently.

So, to allow the access token to be permanently updated with a refresh token obtained once, The refresh token should be changed over with a new refresh token that is obtained at the same time each time the access token is updated.

Based on the above, the following is a sample of updating an access token and retrieving user information using the access token.

import * as dotenv from 'dotenv';
import * as fs from 'fs';
import * as simpleOauth2 from 'simple-oauth2';
import axios from 'axios';
dotenv.config();

const oauth2 = simpleOauth2.create({
    client: {
        id: process.env.CLIENT_ID,
        secret: process.env.CLIENT_SECRET
    },
    auth: {
        tokenHost: "https://login.microsoftonline.com/common",
        authorizePath: "/oauth2/v2.0/authorize",
        tokenPath: "/oauth2/v2.0/token"
    }
});

// Save the token.
const TOKEN_PATH = './data/token';

async function getToken() {

    let graphToken: any = {
        "access_token": "",
        "refresh_token": "",
        "expires_in": ""
    };

    if (fs.existsSync(TOKEN_PATH)) {
        // If a token is stored, use it.
        graphToken = JSON.parse(fs.readFileSync(TOKEN_PATH).toString());

        const accessToken = oauth2.accessToken.create(graphToken);
        if (!accessToken.expired()) {
            // Return access token if token has not expired
            return graphToken.access_token;

        }
    } else {
        // No tokens are saved, so use the first refresh token you set.
        graphToken.refresh_token = process.env.REFRESH_TOKEN;
    }

    // Update token
    const accessToken = oauth2.accessToken.create(graphToken);
    const token = await accessToken.refresh();

    // Save the token
    graphToken = token.token;
    fs.writeFileSync(TOKEN_PATH, JSON.stringify(graphToken));

    // Return access token
    return graphToken.access_token;
}

// Graph API Testing
async function testGetMe(token: string) {
    const url = `https://graph.microsoft.com/v1.0/me`;

    const res = await axios.request({
        headers: {
            'authorization': `Bearer ${token}`
        },
        url,
        method: "GET"
    });

    return res.data;
}

(async () => {
    try {

        const access_token = await getToken();

        const user = await testGetMe(access_token);
        console.log(user);

    } catch (err) {

        console.log(err);

    }
})();

supplementary explanation

  • Using simple-oauth2 to obtain an access token from a refresh token
  • For the first time, the access token is obtained from the refresh token obtained from the web app
  • Refreshing the token returns both an access token and a refresh token
  • The updated tokens are saved as a file in data/token.
  • The second migration uses the saved tokens
  • (We use "2.2.1" because an error occurs if the version of simple-oauth2 is 3 or higher.
  • As a sample of token usage, we use testGetMe() to obtain our own information.

Impressions, etc.

Since the token we have obtained is for a single Office365 user, the scope of privileges is limited to what that user can do. In other words, you cannot see other users' emails.

How to obtain a token as an administrator application is described in a separate article.

www.ekwbtblog.com

It may seem exaggerated since we are setting up a web server, but we need to get redirected after consent, so we need to set up a web server.

Reference Articles

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com