Manav.id
Security ยท 16 min read

You changed the password. The app you authorised in 2023 is still reading your mail.

A password reset feels like closing a door. For OAuth consent grants it is not even a gesture at the door. The grant was a separate act, it lives in a separate place, and in most configurations it keeps working long after the incident ticket is marked resolved.

The incident that was closed twice

Picture a security team at a professional services firm on a Monday morning. A partner's account was phished the previous week. The response ran the way it is supposed to run: password reset, all sessions revoked, multi-factor authentication re-registered on a new device, mailbox rules reviewed and one suspicious forwarding rule deleted, a note written, the ticket closed.

Three weeks later a client calls about an invoice with the wrong bank details. The invoice came from the partner's real address. Nobody at the firm sent it.

The second investigation finds what the first one missed. During the original compromise the attacker had spent about ninety seconds doing something that generated no alert: authorising a third party application against the partner's account. The application had an unremarkable name of the sort you would skim past in a list of enterprise applications. It requested permission to read and send mail, and it requested offline access, which is the scope that grants a refresh token.

The password reset did not touch it. The session revocation did not touch it. The new phone and the new authenticator did not touch it. The forwarding rule that got deleted was the noisy artifact; the grant was the quiet one. For three weeks the attacker had been reading the partner's mail through an approved integration, using tokens issued by the identity provider, appearing in logs as a legitimate application doing legitimate things.

Short answer: An OAuth consent grant is separate from a password and separate from a session. Resetting a password does not revoke a grant, and neither does re-enrolling MFA. A grant that includes offline access issues refresh tokens that can persist for months. This is why attackers add a grant during a compromise: it is the cheapest persistence available and it survives standard incident response.

What is an OAuth consent grant, precisely?

Most people who have clicked one could not define it, which is a large part of the problem. It is worth separating three things that are routinely confused, because the confusion is exactly where the persistence hides.

An access token is a ticket

An access token is a short lived bearer credential. It says an application may act with certain permissions, usually for something like an hour. It is presented on each API call. When it expires it is useless. If access tokens were the whole story, revocation would be easy and this article would not exist.

A refresh token is a ticket printing machine

A refresh token is the durable one. The application presents it to the token endpoint and receives a fresh access token, without the user being present, without any prompt, indefinitely, subject to policy. An application holding a valid refresh token does not need you to be logged in. It does not need you to be awake. It does not need your password, because the whole design intent is that it should never have had your password.

This is not a flaw. It is what makes background integrations work. Your calendar app syncs at three in the morning because it holds a refresh token. The design is correct for its purpose. The trouble is what happens when the party holding it is not the party you thought.

A grant is the standing permission both come from

Behind both tokens sits the grant, sometimes called the consent or the authorisation. It is the record that says this user approved this application for these scopes. Tokens are derived from it. Revoking a token removes a ticket. Revoking the grant switches off the machine that prints them.

Here is the crux, and it is the single most useful sentence for an incident responder to internalise: the password, the session, and the grant are three independent things, and standard account recovery touches only the first two.

Response actionKills the session?Kills the password?Kills the OAuth grant?
Password resetSometimes, depending on policyYesNo
Revoke sign-in sessionsYesNoNo
Re-register MFANoNoNo
Disable the user accountYes, for interactive sign-inEffectivelyNot necessarily, and behaviour varies
Delete malicious inbox rulesNoNoNo
Revoke the application grantNoNoYes, this is the one

Look at that table for a moment. Five of the six actions on it are the ones people actually perform during account recovery, and none of them is the one that ends the access. The sixth requires an administrator to know that the grant exists, to find it in a list that may contain hundreds of entries, and to recognise it as hostile.

Why do attackers use consent grants for persistence?

Because it is the highest return action available in a compromised session, and it takes about a minute.

Consider the alternatives from the attacker's perspective. Installing malware is loud, requires an endpoint foothold, and will be found by a modern detection product. Creating a new user account is loud and shows up in provisioning reviews. Adding an authenticator to the victim's account is moderately loud and gets undone by exactly the recovery process above. Setting a forwarding rule is quiet but well known, and it is on every incident response checklist because it has been abused for a decade.

Adding an OAuth grant is quieter than all of them, survives all of the recovery steps, and produces telemetry that looks like an employee adopting a productivity tool. It is native functionality being used as intended, by a party who should not be using it. There is no exploit to detect because nothing was exploited.

Public incident response guidance from Microsoft has treated illicit consent grants as a standing category for years, with documented playbooks for detection and remediation, which is itself evidence that the technique is common enough to warrant a dedicated procedure. Security vendors including Proofpoint and Huntress have reported on consent phishing campaigns, and reporting on adversary in the middle kits describes attackers chaining stolen sessions into OAuth grants for exactly this durability. Treat published counts as directional; there is no authoritative census of how many compromises include a grant, and responder telemetry reflects the population each firm serves.

What is not in dispute is the mechanism, and the mechanism alone justifies the attention.

How does consent phishing work as an initial vector?

Persistence is the second use. The first is getting in without ever asking for a password.

An attacker registers an application with a name calculated to be unremarkable or actively reassuring. Names in the observed pattern tend to look like internal tooling or security products, because a user who reads the name is more likely to approve something that sounds like it came from their own IT department. The attacker then sends a normal looking link that starts an authorisation request against the real identity provider.

The victim sees the genuine consent screen, on the genuine domain, listing permissions in the provider's own interface language. They approve. No password was entered on any attacker page, and no multi-factor prompt was defeated, because the attacker never needed to authenticate as the victim at all. They only needed the victim to authorise them.

If that sounds structurally familiar, it is the same family as device code phishing, which we cover separately: a genuine page, a genuine authentication, and a user approving something whose consequences were never rendered in a form they could evaluate. Both are instances of a pattern we catalogue in the Identity Failure Map, and both differ from the adversary in the middle attacks where the attacker at least has to run infrastructure of their own.

The scopes problem

Consent screens list permissions, and the listing is honest. It is also, for most users, unreadable in the sense that matters.

Consider what a user actually sees. A scope like Mail.Read is comprehensible. A scope like Files.ReadWrite.All looks similar in shape and length, and it is dramatically more dangerous, because that final All means every file the user can reach rather than files belonging to the application. The visual difference between a modest scope and a sweeping one is a few characters. The practical difference is the entire document estate.

Then there is offline_access, which sits in the list looking like the least interesting item on it. It is the one that turns a session into a standing arrangement.

Nobody should be surprised that users approve these. The interface offers a wall of similar looking strings and a blue button, in a moment when the user is trying to accomplish something else. Blaming users for this is like blaming pedestrians for a missing pavement.

What have the providers already fixed?

A fair amount, and a post that ignored it would be misleading.

Microsoft and Google have both moved considerably. User consent can be restricted so that ordinary users cannot approve applications at all, or can approve only applications from verified publishers with low risk permissions. Admin consent workflows let a user request an application and route the decision to someone whose job includes evaluating it. Publisher verification gives a signal about who actually registered an application. App governance and risk based consent blocking can flag or stop applications exhibiting suspicious permission patterns or behaviour. Both providers publish incident response guidance specifically for illicit grants.

These are real improvements and organisations that have implemented them are meaningfully better off. If you have not restricted user consent in your tenant, that is a higher priority than anything else in this article.

So the argument here is not that the providers have been negligent. It is that two structural gaps remain after you have done everything they recommend.

What are the two gaps that remain?

Gap one: consent is granted once and never re-affirmed

You approved an application in 2023. Since then the application may have been sold to a different company. Its data handling may have changed. Its security posture may have degraded. Its requested scopes may have expanded through incremental consent. Your relationship with the vendor may have ended entirely.

The grant does not care. It is a decision made once, at a moment when the facts were different, and it persists on the strength of that moment indefinitely. There is no re-affirmation, no expiry proportional to sensitivity, and usually no notification when something material changes.

No other high consequence permission in an enterprise works this way. Physical access badges get reviewed. Privileged roles get recertified, often quarterly, because auditors insist. Financial approval limits get revisited. The permission that lets a third party read every file in your document store is the one nobody revisits, because it was created by a click rather than by a process.

Gap two: nobody can enumerate their own authority

Ask an administrator which applications hold live grants in their tenant and they can produce a list, though in a large tenant it may run to hundreds of entries with no obvious way to sort the dangerous from the dull.

Now ask an individual employee which applications can read their mail right now, across their work account, their personal account, their GitHub, and their Google Drive. They cannot answer. Not because they are careless, but because there is no place that answers it. Each provider has its own page, in its own menu, under its own name, and no view spans them.

We call this Authority Opacity, and it is the same failure we described for AI agents in the authority graph. There, no operator could say what their agents were permitted to do. Here, no person can say what their applications are permitted to do. It is one failure appearing in two populations, and it is getting worse in both, because agentic applications now request the kind of sweeping scopes that used to be unusual. An assistant that reads your mail, writes files, and sends messages on your behalf needs precisely the permission set that an attacker would ask for.

What would a signed grant change?

The proposal follows from the diagnosis rather than from a product roadmap, and it has three parts.

Render it properly. The grant request is turned into a canonical object and displayed on the user's enrolled device in language about consequences rather than scope identifiers. Not Files.ReadWrite.All, but: this application will be able to read and change every file in your work account, including files other people shared with you.

Sign what was rendered. The user's approval is a cryptographic signature over the canonical bytes of that object. This means the approval cannot be transplanted onto a different grant, and the record of who approved what is a verifiable fact rather than a database row that says a click occurred.

Give it a lifetime and a handle. The grant carries an expiry proportional to its sensitivity, and a revocation identifier that can be used to switch it off by reference.

The canonical object looks like this:

{
  "typ": "grant.request",
  "app": {
    "client_id": "aeb1f2c0-51d3-4a9e-9d05-2b7c8e3f1a44",
    "display_name": "Northwind Mail Assistant",
    "publisher_verified": false,
    "first_seen_in_tenant": "2026-09-15"
  },
  "scopes": [
    {"id": "Mail.Read",            "means": "read all of your email"},
    {"id": "Mail.Send",            "means": "send email as you"},
    {"id": "Files.ReadWrite.All",  "means": "read and change every file you can access"},
    {"id": "offline_access",       "means": "keep doing this when you are not signed in"}
  ],
  "grant_ttl_days": 90,
  "revocation_id": "rvk_8Jd2m1QpX",
  "requested_at": "2026-09-15T09:41:02Z"
}

And the check the authorisation server performs before issuing anything:

canonical = canonicalize(grant_request)
digest    = sha256(canonical)

assert receipt.payload_digest == digest             # signed exactly this
assert verify_ed25519(receipt.sig, digest, pubkey)  # by this enrolled human
assert receipt.expires_at > now()                   # still within lifetime
assert not revoked(receipt.revocation_id)           # not switched off

issue_refresh_token(bind_to=receipt.revocation_id)

The final line is the one that matters for the story at the top of this article. The refresh token is bound to the receipt. Revoking the receipt invalidates the token, everywhere, in one operation, rather than requiring an administrator to find an unremarkable entry in a long list and recognise it as hostile. That is the property we describe in revocation that propagates: authority anchored in a signature can be withdrawn at the anchor.

It also changes what the second investigation looks like. Instead of asking whether a grant is suspicious, a responder asks which grants carry a valid receipt from an enrolled human device, and which do not. A grant created by an attacker inside a stolen session has no receipt. It is not a judgment call any more. It is a query.

Honest limits

This requires the identity providers to play. Microsoft and Google own the consent screens for their own ecosystems. A third party cannot insert a signature requirement into Entra ID's native consent flow without a hook that does not currently exist. What is available today is signed authorisation for applications that integrate it themselves, and for organisations that route high value application approval through their own flow. Manav has not shipped native Entra or Workspace consent integration, and describing it otherwise would be dishonest.

A signature does not evaluate the application. A user who reads the plain language rendering, understands it, and approves a malicious application anyway has produced a perfectly valid signature. This control makes the decision informed and attributable. It does not make it correct. Application vetting is a separate discipline and remains necessary.

Consent fatigue is real and can make things worse. If every application requires a ceremony, users will learn to perform the ceremony without reading, which reproduces the original problem with extra steps. The design has to be proportional: sweeping scopes and long lifetimes get the full treatment, and a calendar application asking to read free and busy times does not.

Some of this may be solved elsewhere. Standards work on continuous access evaluation and shared signals aims at faster revocation propagation, and if it is widely adopted it addresses part of the problem without receipts. Rich authorization requests (RFC 9396) give scopes more structure, which makes better rendering possible. These are complementary and we would rather they succeeded than not.

Service principals are outside this. Applications that authenticate as themselves, with no user involved, are a large part of any tenant and no human signature applies to them. They need their own controls, and pretending otherwise would leave a reader with a false sense of coverage.

What to do this week

None of this requires a purchase. All of it requires an hour.

  1. Add grant revocation to your account recovery runbook. This is the single highest value change in this article. If your runbook says reset password, revoke sessions, re-register MFA, it is incomplete, and the gap is exactly the one that let the invoice go out three weeks later.
  2. Audit enterprise applications in your tenant. Sort by permissions rather than by name. Anything holding mail send, mail read, or write access across all files deserves an owner and a justification. Most tenants find at least one application nobody can account for.
  3. Restrict user consent if you have not. Allow low risk permissions from verified publishers and route everything else to an admin consent workflow. This is a provider feature, it costs nothing, and it removes the easiest version of the attack.
  4. Check your own personal grants. Do it for yourself before you ask anyone else to. Microsoft, Google, GitHub, Slack and most major services have a page listing connected applications. Most people find something from a job they left.
  5. Alert on new grants with high privilege scopes. Especially mail send, mail read, and broad file access. The legitimate rate is low, so the alert is workable.
  6. Set a recertification cadence. Quarterly for anything with sweeping scopes, annually for the rest. Treat it like privileged role review, because that is what it is.
  7. When you close an account compromise ticket, list the grants you checked. Not that you checked, but which ones. The discipline of naming them is what catches the one that does not belong.

If you want to see what a rendered, signed authorisation feels like in practice, there is a working demonstration at /labs/sign/, and the mechanics are documented in the developer documentation.

Frequently asked questions

Does changing my password revoke OAuth app access? No. The grant is a separate record from your credential. Changing a password may end active sessions depending on policy, but an application holding a refresh token continues to obtain new access tokens without any user involvement. You have to revoke the grant itself, which is a different action in a different part of the interface.

What is an illicit consent grant? It is an OAuth authorisation obtained by an attacker, either by tricking a user into approving a malicious application or by creating the grant from inside an already compromised session. It provides durable access that behaves like a legitimate integration and survives password resets and MFA re-enrolment.

Why did our incident response miss it? Because most runbooks were written when passwords and sessions were the whole picture. The steps they specify are correct and insufficient. Grant review is a separate action that many procedures do not include, and the artifact does not look hostile in a list of enterprise applications.

Does admin consent solve this? It helps by reducing how many people can approve applications, and it is worth enabling. It does not change the nature of the approval, which remains an unsigned click in a session. It concentrates the same decision in fewer hands, which also makes those accounts a more attractive target.

How long do refresh tokens last? It depends on the provider and the policy, but the practical answer is long enough to matter, often months, and they can be renewed by use. Treat any grant with offline access as standing access until explicitly revoked rather than as something that will lapse on its own.

How do I audit which apps have access to my account? Each provider has a page for it. In Microsoft accounts and Entra ID it is under connected or enterprise applications, in Google under third party apps with account access, and GitHub, Slack and most major services have equivalents. There is no cross provider view, which is part of the problem this article describes.

Do agentic AI applications make this worse? Yes, structurally. An assistant that reads your mail, writes files and sends messages on your behalf needs the same sweeping permissions an attacker would request, so the scopes that used to be a warning sign are becoming ordinary. That makes permission based judgment harder and attribution more important.

Sources

  1. IETF, RFC 6749: The OAuth 2.0 Authorization Framework, for the grant, access token and refresh token model.
  2. IETF, RFC 9396: OAuth 2.0 Rich Authorization Requests, which gives authorisation detail more structure than plain scopes.
  3. Microsoft Learn, Detect and remediate illicit consent grants, the standing response playbook.
  4. Microsoft Learn, Configure how users consent to applications in Microsoft Entra ID.
  5. Google Workspace Admin Help, Control which third party and internal apps access Workspace data.
  6. Microsoft Security Blog, research on adversary in the middle phishing kits and subsequent OAuth persistence.
  7. Proofpoint, threat insight reporting on consent phishing and cloud account compromise.
  8. OpenID Foundation, Shared Signals working group, including Continuous Access Evaluation Profile work on revocation signalling.
  9. FBI Internet Crime Complaint Center, 2025 Internet Crime Report, for the business email compromise losses that follow mailbox access.
A password is something you know, a session is somewhere you are, and a grant is something you gave away. Only one of the three is still working next month.