← Back to Blog

Demystifying the Microsoft Graph API: A Complete Guide to OAuth 2.0 and App Permissions

Understanding exactly how to authenticate your custom applications against the Microsoft Graph API is the hardest part of M365 development. Here is a massive breakdown of OAuth 2.0 flows, Delegated vs. Application permissions, and how to acquire your first access token securely.

Demystifying the Microsoft Graph API: A Complete Guide to OAuth 2.0 and App Permissions

The Modern Backbone of Microsoft 365

If you are building applications that interact with Exchange Online emails, SharePoint document libraries, Teams messages, or Entra ID user profiles, you are using the Microsoft Graph API. It is the single unified endpoint (https://graph.microsoft.com) that exposes the entire Microsoft cloud ecosystem.

However, the number one roadblock developers face isn't mastering the REST endpoints—it is getting through the front door. Microsoft's authentication mechanisms are notoriously complex, heavily relying on the OAuth 2.0 protocol and robust Entra ID (formerly Azure AD) App Registrations.

In this guide, we are going to demystify Graph API authentication, focusing specifically on how to obtain your first bearer token whether you are writing a background daemon service or an interactive single-page application.

Step 1: Entra ID App Registration

Before you write a single line of code, your application needs an identity within your Microsoft 365 tenant. The Microsoft identity platform needs to know exactly who is asking for data, what data they want, and what level of consent they have been granted.

  • Navigate to the Microsoft Entra admin center.

  • Go to Applications > App registrations and click New registration.

  • Name your application (e.g., Invoice Processing Daemon).

  • Select the supported account types (usually Accounts in this organizational directory only for internal line-of-business apps).

  • Click Register.

You will receive an Application (client) ID and a Directory (tenant) ID. Save these; they are the username and domain for your application.

Step 2: Delegated vs. Application Permissions

This is where 80% of developers get confused. Microsoft Graph requires explicit permissions, but there are two distinct ways to request them:

Delegated Permissions (On Behalf of a User)


Use Delegated permissions when your application has a signed-in user. For example, a React SPA where an employee logs in and views their own calendar. The application acts strictly on behalf of the signed-in user and cannot access data the user themselves cannot access.
* Best for: Web apps, mobile apps, SPAs, SPFx web parts.
* Flow: Authorization Code Flow, Implicit Grant (deprecated), or On-Behalf-Of Flow.

Application Permissions (Daemon / Background Services)


Use Application permissions when your application runs as a background service or daemon without a signed-in user. For example, a nightly Node.js script that scans every employee's OneDrive for compliance violations. This requires high-level tenant Administrator consent because the app is acting as itself, not as a human.
* Best for: Cron jobs, Azure Functions, automation scripts, server-to-server calls.
* Flow: Client Credentials Flow.

Step 3: Generating Client Secrets or Certificates

If you are using Application Permissions (Client Credentials Flow), your app needs a password to prove its identity alongside its Client ID.

Under Certificates & secrets in your App Registration:
* Client Secrets: A simple string password. It is easy to use for development but requires regular rotation (max 24 months).
* Certificates: Uploading a public key (.cer) while your app signs requests with a private key. This is highly recommended for production workloads and required for highly sensitive Graph endpoints.

Step 4: Acquiring the Access Token

Let's look at the absolute simplest way to acquire an access token using the Client Credentials Flow (Application Permissions) via standard HTTP requests in Node.js, without relying on the heavier MSAL (Microsoft Authentication Library) SDKs.

This is perfect for server-side automation scripts:

async function getGraphToken() {
const tenantId = 'YOUR_TENANT_ID';
const clientId = 'YOUR_CLIENT_ID';
const clientSecret = 'YOUR_CLIENT_SECRET';

const tokenEndpoint = https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token;

const params = new URLSearchParams();
params.append('client_id', clientId);
params.append('scope', 'https://graph.microsoft.com/.default');
params.append('client_secret', clientSecret);
params.append('grant_type', 'client_credentials');

const response = await fetch(tokenEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: params
});

const data = await response.json();
return data.access_token;
}

Why .default?


Notice the scope: https://graph.microsoft.com/.default. When using the Client Credentials flow, you cannot dynamically request specific scopes (like Mail.Read) in the request. Instead, you use .default, which tells Entra ID: "Give me an access token that contains every single Application permission an admin has statically consented to in my App Registration."

Step 5: Making the Graph Call

Once you have the access_token, making the actual Graph API call is incredibly simple. You just inject it into the Authorization header as a Bearer token.

async function fetchUsers(token) {
const graphEndpoint = 'https://graph.microsoft.com/v1.0/users?$top=5';

const response = await fetch(graphEndpoint, {
method: 'GET',
headers: {
'Authorization': Bearer ${token},
'Content-Type': 'application/json'
}
});

const users = await response.json();
console.log(users);
}

Common Authentication Errors

* 401 Unauthorized (InvalidAuthenticationToken): Your token has expired (they last 60 minutes) or was tampered with.
* 403 Forbidden (Authorization_RequestDenied): You successfully authenticated, but you do not have the right permissions. Did you assign User.Read.All in the portal but forget to click Grant admin consent for Contoso?
400 Bad Request (AADSTS7000215): Invalid client secret. You copied the Secret ID instead of the Secret Value*, or the secret has expired.

Conclusion

The Microsoft Graph API is incredibly powerful, but unlocking it requires a solid understanding of Microsoft Entra ID and OAuth 2.0 flows. By strictly separating your understanding of Delegated vs. Application permissions and leveraging the correct credential flows, you can stop fighting 401 errors and start building robust M365 integrations.

Free Developer Tool

M365 Architecture Canvas

Design SharePoint, Teams, and Power Platform architectures visually. Export a structured Markdown document ready for proposals or GitHub.

Try It Free →