Build the mental model
Cybersecurity Basics already covered treating API keys as secrets — this lesson extends that idea to tokens and sessions that keep a user logged in.
A token or session identifier is, functionally, a credential: whoever holds it can act as that user until it expires or is revoked.
- Never log a token, even temporarily — logs are copied, shipped, and kept far longer than expected.
- Never commit a token to Git — history preserves it forever, even after later removal.
- Never expose a server-only token to client-side code — anyone can read it in developer tools.
- Always send tokens over HTTPS — plaintext transit can be intercepted.
Session security follows the same logic: secure, HttpOnly cookies, SameSite settings, and a real expiry policy all matter.
- Secure, HttpOnly cookies keep a session token out of reach of client-side scripts.
- SameSite settings limit when a cookie is sent along with cross-site requests.
- Sessions should expire after a reasonable period of inactivity.
- Sessions must be fully invalidated the moment a user logs out.
None of this should be built from scratch — established, audited libraries and frameworks have already solved these problems.
TOKEN / SESSION LIFECYCLE
-------------------------
ISSUED (login succeeds)
|
v
USED -- must be over HTTPS, never logged, never in Git
|
+-- server-only token? --> NEVER expose to client-side code
|
v
EXPIRES (timeout) or INVALIDATED (logout)
|
v
DEAD -- token/session no longer usableConnect it to a real scenario
The function below acts as a quick, automated reviewer: describe the code's behavior as four booleans, and it reports exactly which mistakes are present.
| Mistake | Why it matters |
|---|---|
| Logging the token | Logs are copied, shipped to tools, and kept long-term. |
| Committing to Git | History preserves it forever, even after later removal. |
| Exposing to client-side code | Anyone using the app can read it in developer tools. |
| Skipping HTTPS | The token can be intercepted in transit. |
Run it against a risky snippet (logs the token and exposes it to the client — RISKY) and a safe one (does none of the four — SAFE).
Use a check like this as a mental checklist during code review, whether the code was written by hand or generated by an AI assistant.
Never roll your own
Never write your own session/token handling from scratch. Use established, audited libraries and frameworks — this is one of the fastest ways to introduce a serious vulnerability if done by hand.
Try the working example
function scanTokenHandling(snippet) {
const mistakes = [];
if (snippet.loggingToken) {
mistakes.push("Token is written to logs — logs are read by many people/tools and often stored long-term.");
}
if (snippet.committingToGit) {
mistakes.push("Token is committed to Git — history keeps it forever, even after later removal.");
}
if (snippet.exposingServerTokenToClient) {
mistakes.push("A server-only token is sent to client-side code — anyone using the app can read it.");
}
if (!snippet.usingHTTPS) {
mistakes.push("Token travels over a non-HTTPS connection — it can be intercepted in transit.");
}
return {
mistakeCount: mistakes.length,
mistakes,
verdict: mistakes.length === 0 ? "SAFE" : "RISKY",
};
}
const riskyExample = scanTokenHandling({
loggingToken: true,
committingToGit: false,
exposingServerTokenToClient: true,
usingHTTPS: true,
});
const safeExample = scanTokenHandling({
loggingToken: false,
committingToGit: false,
exposingServerTokenToClient: false,
usingHTTPS: true,
});
console.log("Risky snippet:");
console.log(JSON.stringify(riskyExample, null, 2));
console.log("\nSafe snippet:");
console.log(JSON.stringify(safeExample, null, 2));Risky snippet:
{
"mistakeCount": 2,
"mistakes": [
"Token is written to logs — logs are read by many people/tools and often stored long-term.",
"A server-only token is sent to client-side code — anyone using the app can read it."
],
"verdict": "RISKY"
}
Safe snippet:
{
"mistakeCount": 0,
"mistakes": [],
"verdict": "SAFE"
}5-minute try-it
Add a fifth check to scanTokenHandling for a token with no expiry set at all (neverExpires: true). Flag it as a mistake with a clear reason, then re-run both the risky and safe examples with this new field added.
One important caution
Logging a token "just for debugging" and forgetting the log itself becomes a long-lived copy of the credential.
Assuming a cookie is safe by default without explicitly setting Secure, HttpOnly, and SameSite.
OWASP Session Management Cheat Sheet — Digital Privacy & Modern Security