Post

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

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

ClaimFull NameWhat it means
subSubjectUnique identifier for the user, but scoped per application. May differ between apps (pairwise).
oidObject IDThe truly stable, tenant-wide unique identifier for the user. This never changes, so use it as your primary key in any database.
tidTenant IDWhich Azure AD / Entra tenant this user belongs to. Critical in multi-tenant apps, as you must check whether this tenant is allowed.
nameDisplay NameThe user’s human-readable name. Can be changed, so never use it as an identifier.
upnUser Principal NameThe login address (usually email). In B2B scenarios this may be the home tenant address, not the guest tenant address.

Trust & Verification Claims

ClaimFull NameWhat it means
audAudienceWho this token is for. The resource server must check: “Is my own URL listed here?” If not, reject the token.
issIssuerWho issued this token. Must always be a trusted Microsoft endpoint. A server that skips this check can be fooled with a fake token.
iatIssued AtUnix timestamp of when the token was created.
expExpirationThe token is invalid after this timestamp. Access tokens typically live ~1 hour.
nbfNot BeforeThe token must not be accepted before this timestamp. Guards against clock skew edge cases.

Permission Claims

ClaimFull NameWhat it means
scpScopeWhich delegated permissions the application has, on behalf of the user. Space-separated. Represents what the app may do.
rolesRolesWhich 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 user
  • roles = application permissions or assigned roles, representing what the entity is

Authentication Context Claims

ClaimFull NameWhat it means
amrAuthentication Methods ReferenceHow the user authenticated. Common values: pwd (password), mfa (multi-factor), wia (Windows Integrated Auth), rsa, otp
auth_timeAuthentication TimeWhen the user last actually authenticated (not when the token was issued).
acrAuthentication Context ClassThe level of authentication assurance. 1 = basic password. Higher values require stronger auth.

Metadata Claims

ClaimFull NameWhat it means
verVersionToken format version. 1.0 vs 2.0 have subtle differences, for example v1.0 uses unique_name instead of upn.
utiUnique Token IdentifierA unique ID per token issuance. Useful for logging and token revocation tracking.
appid / azpApplication IDThe 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 exp check → expired tokens accepted forever
  • No iss check → forged tokens from a custom issuer accepted
  • No scp check → app can call endpoints beyond what the user consented to
  • No tid check in multi-tenant apps → users from unauthorized tenants get in

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

ToolWhat it does
jwt.msMicrosoft’s JWT debugger - beautifully annotates every claim
MSALMicrosoft Authentication Library - request tokens programmatically
AADInternalsPowerShell module for Entra ID research, by Dr. Nestori Syynimaa
TokenTactics v2Token research tooling for legitimate use
Burp Suite / FiddlerIntercept and inspect authentication traffic

  1. RFC 7519 - The JWT specification. Short and readable.
  2. Microsoft Identity Platform docs - learn.microsoft.com/en-us/entra/identity-platform/
  3. dirkjanm.io - Dirk-jan Mollema’s blog. The go-to reference for Entra ID / Azure AD research.
  4. AADInternals - Dr. Nestori Syynimaa (author of AADInternals). Deep dives into token mechanics.
  5. 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


This post is licensed under CC BY 4.0 by the author.