ASVS 5.0 V10.3.4 says that if a resource server requires a specific authentication strength, method or recentness, it verifies that the presented access token satisfies it, for example with the OIDC acr, amr and auth_time claims. It is a Level 2 requirement.
Why it matters
A user who signed in with a password yesterday should not be able to move money today because they hold a valid token. Sensitive endpoints need to know how and when the user authenticated.
How to implement it
Decide the bar per operation, then check the claims.
function requireStrongRecent(maxAgeSeconds: number) {
return (req, res, next) => {
const { amr = [], auth_time } = req.auth;
const now = Math.floor(Date.now() / 1000);
const strong = Array.isArray(amr) && amr.includes("mfa");
const fresh = typeof auth_time === "number" && now - auth_time <= maxAgeSeconds;
if (strong && fresh) return next();
res.status(401).set("WWW-Authenticate",
'Bearer error="insufficient_user_authentication", max_age="' + maxAgeSeconds + '"').end();
};
}
app.post("/payments", requireStrongRecent(300), createPayment);
The 401 response tells the client to send the user back through authorization with a fresh max_age and the needed acr_values. Check which values your provider issues, since acr strings are provider specific.
How to test it
Use a token from a password only login and call the sensitive endpoint. Expect the step up response. Repeat after MFA. Then wait past the age limit and expect it again.
Common mistakes
- Assuming MFA happened because the client asked for it.
- Missing
auth_timeand treating that as fresh. - Hardcoding
acrstrings that differ per provider.
Quick check
Which claim tells you when the user last authenticated?
auth_time.