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

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:
The user logs in using Microsoft (via OAuth 2.0/OpenID Connect).
Microsoft returns a JWT token (ID token + optional access token).
My backend needs to validate that token.
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:
Library | Purpose |
Azure.Identity | To create credentials for app-based or user-based flows |
Microsoft.Graph | To interact with Microsoft 365 services like /me |
System.IdentityModel.Tokens.Jwt | To parse and validate JWT tokens |
Microsoft.IdentityModel.Protocols.OpenIdConnect | To 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.
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.