Breaking Down My First Microsoft 365 Integration in .NET — Token Validation, Graph API, and What I Learned

Peter  ImadePeter Imade
3 min read

Introduction

I was recently assigned a task at work: complete the Microsoft 365 authentication flow in one of our external applications. My colleague had already done some groundwork, but the integration was incomplete — and it was up to me to figure out the rest.

This blog post breaks down:

  • How Microsoft authentication actually works behind the scenes

  • The real use of libraries like Microsoft.IdentityModel.Tokens and Microsoft.Graph

  • How I validated tokens, authenticated users, and accessed their profile data via Microsoft Graph

If you're integrating Microsoft 365 into your .NET app, this guide will save you hours of head scratching.

Step 1: Understanding the Flow

Before diving into code, I had to understand the sequence:

  1. The user logs in using Microsoft (via OAuth 2.0/OpenID Connect).

  2. Microsoft returns a JWT token (ID token + optional access token).

  3. My backend needs to validate that token.

  4. If valid, I can fetch user info using Microsoft Graph API.

The Tools I Used

Here’s the breakdown of the libraries that made everything work:

LibraryPurpose
Azure.IdentityTo create credentials for app-based or user-based flows
Microsoft.GraphTo interact with Microsoft 365 services like /me
System.IdentityModel.Tokens.JwtTo parse and validate JWT tokens
Microsoft.IdentityModel.Protocols.OpenIdConnectTo pull Microsoft’s signing keys & config

The Service I Built

To keep things clean and testable, I built a service called MicrosoftIdentityService. Here’s what it does.

1. Token Validation

public async Task<(bool IsValid, string? Email)> ValidateTokenAsync(string token)
  • Downloads Microsoft’s OpenID config and public keys

  • Validates the token’s:

    • Signature

    • Expiry

    • Audience (my app’s client ID)

    • Issuer (Microsoft)

  • Extracts the user’s email from common claim types like:

    • email

    • preferred_username

    • upn

Why this matters:
This is how you make sure the token wasn’t tampered with and that it’s really from Microsoft.


🔐 2. App-Only Authentication (Client Credentials)

public GraphServiceClient CreateClient()
  • Uses the app’s client ID + secret to authenticate with Microsoft (no user involved)

  • Perfect for background jobs or admin-level access

  • Returns a GraphServiceClient you can use like:

var users = await graphClient.Users.GetAsync();

🔄 3. On-Behalf-Of Authentication (User-based)

public GraphServiceClient CreateClient(string idToken, string[]? scopes)
  • Uses the user’s token (from frontend or redirect)

  • Creates an OnBehalfOfCredential

  • Returns a GraphServiceClient scoped to that user

✅ This lets you do stuff like:

var profile = await graphClient.Me.GetAsync();

👤 4. Fetch User Profile

public async Task<User?> GetUserProfile(string accessToken, string[] scopes)
  • Wraps the whole flow:

    • Builds a GraphServiceClient for the user

    • Calls Microsoft Graph to get user’s full profile

What I Learned

  • JWT validation isn’t magic — it’s just config, keys, and careful checking.

  • Scopes matter. Without User.Read, you won’t get user profile info.

  • Microsoft Graph is powerful — once you’re authenticated properly, you can access almost everything in Microsoft 365.

  • Writing services like this keeps your code modular, testable, and reusable.

Tips If You're Building This

  • Use jwt.ms to inspect any token manually.

  • Always validate tokens before trusting their claims.

  • Make sure your Azure App Registration has the right permissions.

  • Use /.default scope to honor pre-configured API permissions.

Conclusion

This task taught me a lot — not just about Microsoft 365, but about writing secure authentication flows and thinking in terms of separation of concerns.

1
Subscribe to my newsletter

Read articles from Peter Imade directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Peter  Imade
Peter Imade

Peter is a Technical writer who has specific interests in software and API documentation. He is also a back-end developer who loves to share his knowledge of programming concepts on his blog and for other publications.