Beta

Create a tenant

A new tenant starts with its own users, OAuth settings, audit history and logs. You are its first Tenant Admin.

BTL Admin

Forged tokens and stolen signing keys

A token nobody issued

The photo application's support console lets staff look up any customer's albums, reset sign-in methods, and close accounts. Every request to it carries a token signed by the photo service's sign-in service, auth.photos.example. The console checks the signature, the issuer, the audience, and the expiry, and if all four pass, it acts as the staff member the token names. It never asks how the token came to exist. It assumes that a person signed in, satisfied whatever the sign-in demanded, and that the sign-in service then wrote and signed this token.

A forged token breaks that assumption. It passes the console's checks, but no sign-in produced it. An attacker can get one in two ways: by obtaining the private key the sign-in service signs with, or by finding a validator that accepts tokens without real proof of that key. Either way, every protection that runs at sign-in is skipped. Passwords, passkeys, multi-factor authentication, and rules about devices and networks all apply to a sign-in, and a forger never goes through one.

On the genuine path, a person signs in, passes the sign-in checks such as a password, passkey, MFA and policy, and the sign-in service records the sign-in and signs a token with its private key. On the forgery path, an attacker who holds a stolen copy of the signing key writes their own claims and signs them, or an attacker without any key sends an unsigned token or one carrying its own key to a weak validator. All of these tokens reach the application, which verifies the signature and grants access as the named user. Only the genuine path leaves a sign-in record. On the genuine path, a person signs in, passes the sign-in checks such as a password, passkey, MFA and policy, and the sign-in service records the sign-in and signs a token with its private key. On the forgery path, an attacker who holds a stolen copy of the signing key writes their own claims and signs them, or an attacker without any key sends an unsigned token or one carrying its own key to a weak validator. All of these tokens reach the application, which verifies the signature and grants access as the named user. Only the genuine path leaves a sign-in record.
A forged token reaches the same application check as a genuine one, but it skips every sign-in protection and leaves no sign-in record behind.

Stolen sessions and replayed tokens, the subject of How sessions and tokens are stolen, start from a real sign-in by the victim. A forgery has no such starting point. That makes it powerful, and it also leaves a gap in the records that defenders can look for.

Stealing the key that signs

Storing and using keys called a token-signing key a key worth stealing, because whoever holds it can create a token saying they are anyone, and every application that trusts the issuer will believe it. With the photo service's key, an attacker can write a token for any staff member, choose its audience and roles, and set its expiry far in the future. SAML works the same way: a stolen assertion-signing key lets an attacker produce assertions that every service provider trusting that identity provider will accept.

Signing keys rarely leak through weaknesses in the cryptography. They leak from the places they are kept and used: a key file on a server, a backup or disk image that includes it, a source repository or build log, a crash dump or debugging snapshot taken while the key was in memory, or a test environment that was given a copy of the production key. An attacker with administrative access to the systems behind the sign-in service may simply read the key.

The damage grows with how widely the key is trusted. A key that signs tokens for one API exposes that API. A key that signs ID tokens and access tokens for every application, or that a provider hosting many organizations uses for all of them, exposes every one of them at once.

Validation that lets forgeries through

Some forgeries need no key at all, only a validator that does not insist on the right one. Decoding a token and reading its claims is not validating it, as Protecting credentials and messages pointed out, and Digital signatures set out the rule for validation itself: the verifier decides for itself which issuers, keys, and algorithms it accepts, and treats the token's header as a hint. Each common validation mistake breaks that rule in its own way.

  • An unsigned token declares its algorithm as none and carries no signature. A validator that honors the declaration accepts claims anyone can write.
  • Algorithm confusion happens when the token chooses how it is checked. If the service signs with a private key, but a validator can be told that the token uses a shared-secret algorithm instead, it may check the token using the public key as the shared secret. The public key is public, so anyone can produce a matching value.
  • A key supplied by the token, in a header field that embeds a public key or points to an address to fetch one from, lets the attacker sign with their own key and tell the validator to use it. The signature verifies, and proves only that whoever wrote the token signed it.
  • Missing issuer and audience checks let a genuine token from somewhere else through: one issued for a different application, or by another issuer whose keys the validator happens to hold.
  • Accepting another tenant's issuer is the multi-tenant form of the same mistake. Providers that host sign-in for many organizations often sign every tenant's tokens with the same keys, so a token from a tenant the attacker created for themselves verifies perfectly. Only the issuer, which names the tenant, shows that it does not come from the organization the application serves.

The fix for all of them is the same. Configure the validator with the issuer it expects, the audience that identifies this application, the algorithms it allows, and where to fetch the issuer's keys, and reject anything outside that configuration. Maintained libraries do this when they are given explicit settings rather than left to their defaults. Validating an ID token and Issuer, audience, and authorized party apply these checks to ID tokens.

Each token below has one flaw. Choose the check that rejects it.

Which check catches it?

Simulation. These decoded tokens are made up, and answering sends nothing. Each is sent to the photo application's business API, which expects the issuer https://idp.example.test/t-4821/, the audience https://business.photos.example, and an ES256 signature from a key in that provider's published key set.

ITEM 1 OF 4

Which check rejects this token?
Header
{ "alg": "none", "typ": "JWT" }

Payload
{
  "iss": "https://idp.example.test/t-4821/",
  "aud": "https://business.photos.example",
  "sub": "u-30562",
  "role": "account-admin",
  "exp": 1790937000
}

Signature
(empty)

Enable JavaScript to check an answer here, or open the explanation below.

Show the answer and why

Requiring a valid signature made with ES256 The API allows ES256 only. A token that declares none and carries no signature must be rejected before any claim is trusted.

ITEM 2 OF 4

Which check rejects this token?
Header
{
  "alg": "ES256",
  "typ": "JWT",
  "jwk": { "kty": "EC", "crv": "P-256", "x": "mT3vQ8kX0aLp...", "y": "Wd2Lq7nZ4cR8..." }
}

Payload
{
  "iss": "https://idp.example.test/t-4821/",
  "aud": "https://business.photos.example",
  "sub": "u-30562",
  "role": "account-admin",
  "exp": 1790937000
}

Signature
verifies with the key in the header

Enable JavaScript to check an answer here, or open the explanation below.

Show the answer and why

Using only keys from the published key set The attacker signed with their own key and put it in the header. A validator that ignores keys supplied by the token finds no trusted key that matches, and rejects it.

ITEM 3 OF 4

Which check rejects this token?
Header
{ "alg": "ES256", "kid": "idp-2026-08", "typ": "JWT" }

Payload
{
  "iss": "https://idp.example.test/t-9930/",
  "aud": "https://business.photos.example",
  "sub": "u-20417",
  "exp": 1790937000
}

Signature
verifies with key idp-2026-08 from the provider's published key set

Enable JavaScript to check an answer here, or open the explanation below.

Show the answer and why

Checking that the issuer is tenant t-4821 The token is genuine, but it comes from tenant t-9930, which anyone can create at the provider. Only the issuer shows that it is not from the customer the API serves.

ITEM 4 OF 4

Which check rejects this token?
Header
{ "alg": "ES256", "kid": "idp-2026-08", "typ": "JWT" }

Payload
{
  "iss": "https://idp.example.test/t-4821/",
  "aud": "https://reports.example.test",
  "sub": "u-30562",
  "exp": 1790937000
}

Signature
verifies with key idp-2026-08 from the provider's published key set

Enable JavaScript to check an answer here, or open the explanation below.

Show the answer and why

Checking that the audience is the business API The token was issued for a reporting service, not the business API. Accepting it would let any application that receives a token from this tenant reuse it here.

Keeping signing keys out of reach

The strongest protection is a key nobody can copy. When the sign-in service's key is generated as non-exportable inside a hardware security module or a cloud key management service, as Storing and using keys describes, an attacker who takes over the sign-in server can request signatures while they have that access but cannot take the key away. Removing their access ends their ability to sign anything new. Tokens they signed in the meantime remain a problem until they expire or the key is replaced, but the incident has an end.

Separate keys for separate purposes limit what one leak exposes: different keys for ID tokens, access tokens, and SAML assertions, for each environment, and, at a provider serving many organizations, for each organization where that is practical. A development key should never be trusted in production.

Access to signing configuration deserves the same care as the keys. Few people and services need permission to manage, export, or replace a key, and every use of that permission should be recorded. Changes to the published key set, to the list of trusted issuers, or to the keys an application accepts are rare, so an alert on each one costs little. An attacker who cannot steal a key may try to add one of their own instead, which Abusing federation and account linking examines.

Noticing a token without a sign-in

Every genuine token has a history. A sign-in created a session, and the session led to the token, or a refresh or a client's own credentials did. The sign-in service records each of those steps, usually with the token's identifier (jti) or a session identifier (sid) that also appears in the token. A forged token has no history. Its identifier was never issued, or it names a session that never existed.

Finding that gap depends on the applications and APIs recording which tokens they accepted, by identifier rather than by value, and on someone comparing those records with the issuer's. The comparison can surface tokens with:

  • an identifier the issuer has no record of issuing;
  • a user who had no sign-in or refresh around the time the token says it was issued;
  • a lifetime longer than the issuer ever grants, or an issue time in the future;
  • a combination of audience, scopes, or roles the issuer never produces;
  • more tokens carrying a key's identifier than the key service recorded signatures for.

Tokens for a support administrator that match no sign-in point to forgery: a stolen key, or a validator that accepted something it should have refused. That is a different problem from a stolen session, which always traces back to a real sign-in by someone. Innocent explanations exist, such as a gap in log collection or clocks that disagree, and they should be ruled out quickly, because if the suspicion holds, the response is drastic.

When a signing key is exposed

A planned key rotation overlaps the old and new keys so that nothing breaks, as Storing and using keys describes. An exposed key cannot wait for that overlap. The photo service generates a new key, publishes it, starts signing with it, and removes the exposed key from its published key set at once.

Removing the key ends trust in everything it ever signed, genuine and forged alike, because a signature cannot tell them apart. Staff and customers holding tokens signed with it are signed out or have to refresh. Applications that cached the old key set keep accepting forgeries until they fetch the new one, so they need to be told to refresh. Partners that configured a SAML signing certificate by hand have to install the new one before sign-in works for them again. An organization that already rotates keys routinely, and knows every verifier and partner that holds its public key, gets through this far faster than one doing it for the first time.

Replacing the key stops future forgeries. It does not undo what earlier ones did. The investigation uses the issuance records to find what the forged tokens were used for, then revokes the sessions and grants they created, reverses the changes they made, and finds out how the key escaped so that the new one does not follow it. Rotating credentials and keys after exposure follows that process in more depth.

Try it in the Lab

PUT IT INTO PRACTICE

Check your understanding

Try these questions before moving on. If an answer isn't right, use the feedback and try again.

0 of 2 answered correctly

Enable JavaScript to answer these questions and save progress in this browser.

QUESTION 1 OF 2The photo service signs tokens with ES256. A validator lets each token's header choose the algorithm, and a forged token names a shared-secret algorithm instead. Why might the forgery pass?

QUESTION 2 OF 2The support console's logs show tokens for an administrator, but the sign-in service has no matching sign-in, refresh, or issued token identifier. What does that suggest?

We value your privacy

We use cookies and similar technologies to enhance your browsing experience, and analytics to understand our traffic. By clicking "Allow All", you consent to optional analytics. Cookie Policy

Learn identity