A Token of Appreciation - JWTs & Microsoft Authentication for Security Research
A practical JWT and OAuth foundation for Microsoft security research: token structure, claims, authentication flows, and where validation goes wrong.
A Token of Appreciation - JWTs & Microsoft Authentication for Security Research
This post covers the fundamentals of Microsoft’s identity platform, how JSON Web Tokens work, what claims actually mean, and why they matter for security research.
I put this post together after spending time reading through Microsoft’s documentation, watching videos on the subject, including the one by Dr. Nestori Syynimaa at the bottom of this page, and write down everything I could find. I’ve done my best to get it right, but I’m still learning. If you spot a mistake or something that could be explained better, feel free to reach out. Or better, grab a crate of beer, drive over to my house, and let’s talk about this over a cold one.
Background
Microsoft’s authentication system is built on two open standards: OAuth 2.0 and OpenID Connect (OIDC), running on top of their own Microsoft Identity Platform, formerly known as Azure Active Directory, now rebranded as Microsoft Entra ID.
If I ever want to do more security research in this space, think of finding bugs worth submitting to MSRC, I need to understand tokens from the ground up. Not just run a tool and hope for the best, but actually know why things work the way they do. That’s what drove me to dig into this.
The Three Token Types You Need to Know
Microsoft’s identity platform issues three distinct token types, each serving a different purpose.
Access Token
The workhorse. Presented to a resource server (like Microsoft Graph) to prove you have permission to call it. Contains claims about who you are and what you’re allowed to do. Intentionally short-lived, typically around 1 hour.
Refresh Token
Used to silently obtain a new access token without requiring the user to log in again. Lives much longer (days or weeks). Stored by the client application. Never sent to resource servers, only to the token endpoint.
ID Token
An OpenID Connect concept. Proves the identity of the user to the application. Contains user profile information like name and email. Not meant to be sent to APIs.
Bonus: Primary Refresh Token (PRT)
A special token tied to Windows devices. Binds authentication to a specific machine. Forms the basis for seamless SSO on Entra-joined devices. Historically a very interesting area for security research.
What is a JWT?
A JWT (JSON Web Token) is three Base64url-encoded strings joined by dots:
1
2
eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyIn0.SflKxwRJSMeKKF2QT4fw
HEADER PAYLOAD SIGNATURE
Base64 is not encryption. Anyone who gets hold of a JWT can decode and read it. What makes it trustworthy is the cryptographic signature, not secrecy of the contents.
You can decode any JWT right now at jwt.ms, Microsoft’s own JWT debugger.
The Three Parts
Header: Metadata about how the token was signed:
1
2
3
4
5
{
"alg": "RS256",
"typ": "JWT",
"kid": "12345"
}
alg: The signing algorithm (RS256 = RSA + SHA-256)typ: Token type (always JWT here)kid: Key ID: tells the receiver which public key to use for signature verification
Payload: The actual claims (more on this below)
Signature: A cryptographic signature over base64(header) + "." + base64(payload), created with Microsoft’s private key. You verify it using Microsoft’s public key, which you can fetch from their JWKS endpoint.
What Is a Claim?
A claim is literally a statement the token makes about the user or the session. The token claims that you are Captain Rex, that you authenticated with MFA, and that you have Mail.Read access.
There are three categories:
Registered claims: standardized by the JWT spec (RFC 7519). Always the same names, understood by every implementation: iss, sub, aud, exp, iat, nbf.
Microsoft-specific claims: extra claims Microsoft adds on top of the standard: oid, tid, upn, scp, roles, amr, ver, etc. Fully documented in the Microsoft Identity Platform docs.
Custom claims: claims you can configure yourself via optional claims in an app registration. Examples: department, employee_id, or any other directory attribute.
A Real Microsoft Access Token - Claim by Claim
Below is a representative Microsoft access token payload with all the important claims explained.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
{
"typ": "JWT",
"nonce": "",
"alg": "RS256",
"x5t": "",
"kid": ""
}.{
"aud": "",
"iss": "",
"iat": 1775157153,
"nbf": 1775157153,
"exp": 1775243853,
"acct": ,
"acr": "",
"acrs": [
""
],
"aio": "",
"amr": [
"pwd",
"mfa"
],
"app_displayname": "",
"appid": "",
"appidacr": "",
"given_name": "Rex",
"idtyp": "user",
"ipaddr": "",
"name": "Captain Rex",
"oid": "",
"platf": "",
"puid": "",
"rh": "",
"scp": "openid Policy.Read.All profile User.Read email",
"sid": "",
"sub": "",
"tenant_region_scope": "EU",
"tid": "",
"unique_name": "",
"upn": "",
"uti": "",
"ver": "",
"wids": [
""
],
"xms_acd": ,
"xms_act_fct": "",
"xms_cc": [
""
],
"xms_ftd": "",
"xms_idrel": "",
"xms_pftexp": ,
"xms_ssm": "1",
"xms_st": {
"sub": ""
},
"xms_sub_fct": "",
"xms_tcdt":,
"xms_tdbr": "EU",
"xms_tnt_fct": ""
}.[Signature]
Identity Claims
| Claim | Full Name | What it means |
|---|---|---|
sub | Subject | Unique identifier for the user, but scoped per application. May differ between apps (pairwise). |
oid | Object ID | The truly stable, tenant-wide unique identifier for the user. This never changes, so use it as your primary key in any database. |
tid | Tenant ID | Which Azure AD / Entra tenant this user belongs to. Critical in multi-tenant apps, as you must check whether this tenant is allowed. |
name | Display Name | The user’s human-readable name. Can be changed, so never use it as an identifier. |
upn | User Principal Name | The login address (usually email). In B2B scenarios this may be the home tenant address, not the guest tenant address. |
Trust & Verification Claims
| Claim | Full Name | What it means |
|---|---|---|
aud | Audience | Who this token is for. The resource server must check: “Is my own URL listed here?” If not, reject the token. |
iss | Issuer | Who issued this token. Must always be a trusted Microsoft endpoint. A server that skips this check can be fooled with a fake token. |
iat | Issued At | Unix timestamp of when the token was created. |
exp | Expiration | The token is invalid after this timestamp. Access tokens typically live ~1 hour. |
nbf | Not Before | The token must not be accepted before this timestamp. Guards against clock skew edge cases. |
Permission Claims
| Claim | Full Name | What it means |
|---|---|---|
scp | Scope | Which delegated permissions the application has, on behalf of the user. Space-separated. Represents what the app may do. |
roles | Roles | Which app roles the user (or app) has been assigned. Represents who the user is and what they are allowed to do. |
The scp vs roles distinction is important:
scp= delegated permissions, meaning the app is acting on behalf of a userroles= application permissions or assigned roles, representing what the entity is
Authentication Context Claims
| Claim | Full Name | What it means |
|---|---|---|
amr | Authentication Methods Reference | How the user authenticated. Common values: pwd (password), mfa (multi-factor), wia (Windows Integrated Auth), rsa, otp |
auth_time | Authentication Time | When the user last actually authenticated (not when the token was issued). |
acr | Authentication Context Class | The level of authentication assurance. 1 = basic password. Higher values require stronger auth. |
Metadata Claims
| Claim | Full Name | What it means |
|---|---|---|
ver | Version | Token format version. 1.0 vs 2.0 have subtle differences, for example v1.0 uses unique_name instead of upn. |
uti | Unique Token Identifier | A unique ID per token issuance. Useful for logging and token revocation tracking. |
appid / azp | Application ID | The client ID of the application that requested the token. appid is used in v1.0 tokens, azp (Authorized Party) is its v2.0 replacement. |
Why the Signature Matters
The signature is what makes the whole system trustworthy. Microsoft signs the token with their private key. You verify it using their public key, available at:
1
https://login.microsoftonline.com/common/discovery/v2.0/keys
The kid claim in the header tells you which key to use. If anyone tampers with even a single byte of the payload (say, bumping scp from User.Read to User.Read.All), the signature check fails.
But only if you actually verify the signature. A server that skips this check will accept any token, including a completely fabricated one.
The Token Flows
Different scenarios use different flows to obtain tokens.
Authorization Code Flow
The most common flow for web applications. The user authenticates in the browser, receives an authorization code, and the backend exchanges that code for tokens.
1
User → redirect to login → Auth code returned → App exchanges code for tokens
Client Credentials Flow
Machine-to-machine. No user involved. The application authenticates with its own credentials (client secret or certificate) and receives an access token directly.
Device Code Flow
For CLI tools and devices without a browser. The user gets a code to enter on another device. This flow is increasingly abused in phishing campaigns. See The Hidden Danger of Device Code Phishing for a deep dive into how attackers exploit it.
On-Behalf-Of Flow (OBO)
A service receives a token for itself, then exchanges it for a token to call a downstream service, on behalf of the original user. Complex flow with interesting security implications.
Interesting Attack Surfaces for Security Research
These are well-known vulnerability categories where MSRC-worthy findings come from. All of this is public knowledge and understanding these patterns is the foundation of legitimate security research.
Audience Confusion
If a server accepts tokens not intended for it (aud validation missing or weak), a token obtained for Service A might work at Service B. This is a classic and well-documented issue.
Missing Claim Validation
Any required claim that isn’t checked is a potential weakness:
- No
expcheck → expired tokens accepted forever - No
isscheck → forged tokens from a custom issuer accepted - No
scpcheck → app can call endpoints beyond what the user consented to - No
tidcheck in multi-tenant apps → users from unauthorized tenants get in
Scope Escalation via Consent Manipulation
Can you trick a user into consenting to more permissions than they intended? Incremental consent flows have had edge cases here.
FOCI - Family of Client IDs
Some Microsoft first-party applications share refresh tokens. If an app belongs to the FOCI family, a refresh token from App A can sometimes be used to obtain a token for App B. Worth understanding in depth.
CAE - Continuous Access Evaluation
Microsoft introduced CAE to revoke tokens faster than the 1-hour window. Edge cases around how revocation signals propagate (or fail to propagate) are an interesting research area.
Tools to Get Started
| Tool | What it does |
|---|---|
| jwt.ms | Microsoft’s JWT debugger - beautifully annotates every claim |
| MSAL | Microsoft Authentication Library - request tokens programmatically |
| AADInternals | PowerShell module for Entra ID research, by Dr. Nestori Syynimaa |
| TokenTactics v2 | Token research tooling for legitimate use |
| Burp Suite / Fiddler | Intercept and inspect authentication traffic |
Recommended Learning Path
- RFC 7519 - The JWT specification. Short and readable.
- Microsoft Identity Platform docs - learn.microsoft.com/en-us/entra/identity-platform/
- dirkjanm.io - Dirk-jan Mollema’s blog. The go-to reference for Entra ID / Azure AD research.
- AADInternals - Dr. Nestori Syynimaa (author of AADInternals). Deep dives into token mechanics.
- MSRC Security Research blogs - msrc.microsoft.com/blog. Study what has been rewarded before to understand the bar.
Tips for Responsible Disclosure to MSRC
- Always test in your own tenant. Never test on production environments you don’t own.
- Document everything reproducibly, including full request/response pairs, screenshots, and exact steps.
- Describe the security impact concretely. What can an attacker actually do? What’s the blast radius?
- Use the MSRC documentation to estimate your own severity before submitting. It sets expectations and helps them alot. Example Report
- Be patient. Microsoft’s triage process takes time, especially for complex authentication issues.
Summary
A JWT is three Base64-encoded chunks. The payload contains claims, which are statements about who the user is, what they’re allowed to do, and how they authenticated. The signature ties it all together and makes it verifiable without calling back to the issuer.
The security of the entire system depends on receivers actually validating every relevant claim. Missing or weak validation is where vulnerabilities live.
Understanding this foundation is step one. From here, the rabbit hole goes deep: PRT mechanics, FOCI token sharing, CAE edge cases, conditional access bypass attempts. All of it builds on the same primitives you’ve just learned.
Additional Resources
[D25] Exploiting Token Based Authentication - Dr. Nestori Syynimaa
Dr. Nestori Syynimaa (author of AADInternals) walks through how token-based authentication can be exploited in practice, which is in line to the theory covered in this post.
TROOPERS25: Finding Entra ID CA Bypasses - The Structured Way - Dirk-jan Mollema & Fabian Bader
A talk from TROOPERS25 by Dirk-jan Mollema and Fabian Bader on finding Conditional Access bypasses in Entra ID. Directly relevant if you want to take your token knowledge further into the access control layer.
My Own MSRC Case
If you are wondering what this kind of research can lead to, here is a case I submitted to MSRC myself. It is about temporary Global Admin rights in Microsoft Entra PIM that do not expire as expected. A real case example of what happens when you start looking closely at how identity and permissions actually behave.
MSRC Case: When Temporary Global Admin Rights Don’t Expire in Microsoft Entra PIM