1. Courses
  2. Cybersecurity
  3. Application Security for Developers
  4. Stop 3

40 minIntermediate

Authentication, sessions and tokens

Most account takeovers do not break cryptography. They walk through a weak password hash, a session that never changes, or a token nobody checks properly. This lesson shows you, as the developer and the reviewer, how to store passwords, run sessions and validate tokens so that one leak or one bug does not hand over every account.

By the end you can

  • Store passwords with Argon2id or bcrypt at OWASP's settings, and upgrade old hashes without forcing resets
  • Defend a login form against credential stuffing without locking real users out
  • Configure a server-side session with the right cookie flags, and rotate its id at sign-in
  • Review JWT code for the classic mistakes and validate every claim that matters
  • Design short access tokens with refresh-token rotation and reuse detection, and plan passkeys with a recovery path

The everyday picture

Think of a hotel. At check-in, reception looks at your ID once, carefully. After that you never show your ID again: you get a key card. The card holds nothing about you. It carries a number that the hotel's system knows. When you check out, or report the card lost, reception switches it off and it stops opening doors at once.

A music festival works differently. At the gate you get a printed wristband with the dates and a hologram. Any steward can check it at a glance without phoning the office, which is why festivals use them: thousands of checks a minute and no central system. But a wristband cannot be switched off. If someone cuts one off a guest and wears it, it works until the last date printed on it.

Web sign-in comes in the same two shapes. A server-side session is the key card: a meaningless id, looked up on every request, and cancelled in a second. A self-contained token such as a JWT is the wristband: signed, checkable anywhere, and valid until it expires. Most of this lesson is about choosing between them, and about the small details where real systems go wrong, such as who hands over the card and how long a wristband lasts.

Storing passwords so a leak is not a disaster

Assume your users table will leak one day: a backup copied to the wrong bucket, an injection bug like the ones in Injection and unsafe input, or an insider. Password storage decides what an attacker can do with that copy. You met the difference between hashing and encryption in Cryptography without the maths; here is how it plays out in code.

Encryption is the wrong tool, because it can be reversed: the key lives on the same servers as the database, so whoever steals one usually gets both. A fast hash such as SHA-256 is wrong too, because one modern graphics card can try billions of SHA-256 guesses a second against a leaked list. You need a , which has three properties:

  • A salt per user. A makes two users with the same password get different hashes, so precomputed tables are useless and each account has to be attacked on its own. Argon2 and bcrypt generate the salt and store it inside the hash string for you.
  • A work factor. You choose how slow one hash is. Tune it so a login costs your server a noticeable fraction of a second. For you that is one hash per sign-in; for an attacker it is the same cost for every guess.
  • Memory hardness. Argon2id and scrypt also need a block of memory for each guess, which makes racks of graphics cards far less effective.

The OWASP Password Storage Cheat Sheet gives concrete settings. Use Argon2id with at least 19 MiB of memory, 2 iterations and a parallelism of 1. If Argon2id is not available, use scrypt. Keep bcrypt for older systems, with a work factor of 10 or more, and remember that bcrypt reads only the first 72 bytes of a password. Where you must use FIPS-approved algorithms, use PBKDF2 with HMAC-SHA-256 and 600,000 iterations.

An Argon2id hash looks like $argon2id$v=19$m=19456,t=2,p=1$…salt…$…hash…. The parameters travel inside the string, which gives you a clean upgrade path. After a successful login you hold the plain password for a moment, so check whether the stored hash uses older parameters and, if it does, hash the password again with today's settings and save it. Accounts upgrade quietly as people sign in. For legacy fast hashes (unsalted MD5 or SHA-1) you cannot wait for logins: wrap every old hash now as Argon2id of the old hash, then replace it with a plain Argon2id hash at that user's next login.

A , a secret key mixed into every hash, is an optional extra; get the salted, slow hash right first.

Quick check

A review of the Partner app finds passwords stored with AES-256 encryption under a key in the app's config. A teammate says it is fine because AES-256 is strong. What is the right response?

Loading the rest of the lesson…