A JSON Web Token, or JWT, is a signed string that a server hands out to say “this request comes from this user, with these rights”. Whoever holds a valid one can present it as a bearer credential until it expires, with no password and no second factor. There is no provider to ask for a new one: a token is valid because of its signature and its expiry date, so the way to stop it is to make it expire or to change the key that signs it. First find out what the token grants, because the answer ranges from nothing to full admin access.
Check your GitHub account for leaked secrets — free
- Provider
- Other
- Severity
- Medium
- Impact
- SaaS account access and billing
- Checked live by LeakWatch
- No
What a JWT looks like
A JWT is three parts separated by dots. The first two are base64 text that start with eyJ, because they encode a JSON object that begins with {":
eyJ…header.eyJ…payload.signature JWT: header, payload, signature
The header and the payload are encoded, not encrypted: anyone can read them. The payload usually holds the user or service it was issued to (sub), who issued it (iss), its audience (aud) and when it expires (exp). Those claims tell you what the token is worth. The third part is the signature, which is what stops anyone from changing the claims. A variant that LeakWatch also reports is the same token encoded once more in base64, which begins with ZXlK.
Do not paste a live token into an online decoder to read it. Decode it locally, or use the claims you can read in your own code. A token that expired long ago, or one that carries only a public role, is far less serious than one that is still valid and carries an admin role.
How JWTs get leaked
- Hardcoded in test code and fixtures. A real token copied from a browser session to make a test pass.
- Saved API requests. Exported Postman collections,
curlcommands with anAuthorization: Bearerheader, HAR files and HTTP client history. - Logs and error reports. A request logged with its headers, then pasted into an issue or a public build log.
- Environment files. Service tokens and API keys that are themselves JWTs, copied into
.envfiles and CI settings in a repository. - Client-side code. A token embedded in a mobile app or in front-end JavaScript. Some are meant to be public, and some are not: see the FAQ.
What to do in the first hour
- Read the claims locally. Decode the payload on your own machine. Note who it was issued to, its role or scope, the issuer, and the
expdate. If it has already expired and carries no long-lived rights, the urgency drops, but the places it came from still need checking. - Identify the issuer. Find which service signs it: your own backend, an identity provider, or a platform such as a backend-as-a-service. The fix is made there, not at the place where you found the token.
- Make it unusable. Choose the strongest option your system offers: revoke the session or the refresh token the JWT came from, add its identifier (
jti) to a deny list if you keep one, or sign out the user everywhere. If the token is a long-lived service token, delete or regenerate it in the service that issued it. - Rotate the signing key if the exposure is serious. Changing the key invalidates every token signed with it, including the leaked one. This logs everybody out, so plan for it. If your system uses key identifiers (
kid), publish a new key and retire the old one. If the key itself leaked, rotation is not optional. - Shorten token lifetimes. A token valid for a few minutes costs little when it leaks, and a token valid for a year is a standing credential. Use short expiries with refresh tokens that you can revoke.
- Look for use you did not make. Search the logs for the token’s subject, session or
jtiaround and after the exposure: requests from unfamiliar addresses, unusual endpoints, data exports. - Clean the repository: remove the token, add the file to
.gitignore, and rewrite history if you want to. See I accidentally pushed an API key to GitHub.
Not sure what else leaked from the same repository? Run a free scan.
Revoke it at the issuer
A JWT has no central revocation list: it is checked by its signature, not by a lookup. Revoking one therefore happens at whatever issued it. For a session token, end the session or revoke the refresh token in your identity provider. For a service or API token, delete or regenerate it in that service’s dashboard. For a token you sign yourself, rotate the signing key, or keep a deny list of identifiers that your servers check on every request. Which of these applies depends on how your application is built, which is why step 2 comes first.
How LeakWatch detects it
LeakWatch has two rules, JWT and JWT Base64. The first matches the three-part shape, with a header and a payload that both begin with ey. The second matches the base64-wrapped form. The severity is medium: a JWT is a credential, but its value depends entirely on what it grants and when it expires, and the rule cannot know either. This type is not checked live, because a token can only be tested against the service that issued it, and LeakWatch does not know which one that is. Treat a detected token as exposed until you have read its claims and checked its expiry.
LeakWatch detects this format but does not check it live: it can tell you the key is exposed, not whether it still works. Assume it does until you have revoked it.
FAQ
Is a JWT in a public repository always a problem?
No. Some JWTs are public by design. A backend-as-a-service often gives you an “anon” or “public” key that is itself a JWT, meant to sit in client code and protected by access rules on the server. A key with an admin or service_role claim, or a user’s session token, is not public. Read the claims before you decide.
Can I just wait for the token to expire?
Only if it expires soon and carries limited rights. A token valid for days or months, or one that grants write access, should be invalidated now. Waiting for the expiry date is waiting for the attacker’s access to run out.
The payload is readable. Does that mean the token is not secret?
The claims are not secret, but the token as a whole still is. Possession of the full string is what grants access, so a token you would not post in public is one you should not commit either.