ASVS 5.0 V7.2.1 (Level 1) requires that all session token verification is performed by a trusted backend service. The browser, a mobile app or any other client the user controls can hold a token, but it must never be the thing that decides whether the token is valid.
Why it matters
Anything running on the client can be edited. If a single page app decides "this JWT looks fine, show the admin screen" and the API behind it trusts the same decision, an attacker only needs to change a few lines in developer tools. Verification belongs where the attacker cannot rewrite the code.
The same applies to internal hops. A microservice that accepts a user id from a header set by another service is trusting that service as the verifier, so that service must actually verify, and the path between them must be closed to outsiders.
How to implement it
Verify on every request at one choke point, such as middleware or a gateway, and make everything downstream depend on the result.
For a reference session token, look it up in a server side store:
async function requireSession(req, res, next) {
const raw = req.cookies.sid;
if (!raw) return res.sendStatus(401);
const hash = crypto.createHash("sha256").update(raw).digest("hex");
const session = await db.sessions.findOne({ hash, revoked: false });
if (!session || session.expiresAt < Date.now()) return res.sendStatus(401);
req.user = { id: session.userId };
next();
}
For a signed token, verify the signature, issuer, audience and expiry with a maintained library, pinning the allowed algorithm:
const claims = jwt.verify(token, publicKey, {
algorithms: ["RS256"],
issuer: "https://auth.example.com",
audience: "orders-api",
});
Do not decode without verifying. Do not read the algorithm from the token header and accept whatever it says.
How to test it
- Send a request with no token, an expired token and a token with a tampered payload. Each must get a 401.
- Take a valid signed token, change the role claim, keep the old signature, and confirm rejection.
- Change the header algorithm to "none" and confirm rejection.
- Search the front end bundle for logic that gates data on token contents. UI hiding is fine, but the API must enforce the same rule.
Common mistakes
- Decoding a JWT in the client and treating the result as truth.
- Verifying at the gateway but leaving internal services reachable directly.
- Skipping audience checks, so a token for one service works on another.
Quick check
Is it fine for the client to read token claims?
Yes, for display purposes. It must never be the control that grants access.