CISSP-117 · Lesson 6 of 12 · 26 min

Secure coding II: authentication, sessions, cryptography and errors

Lesson 5 dealt with data crossing trust boundaries. This lesson covers the other half of the core secure coding practices: how applications prove who users are and keep track of them, how they use cryptography, how they handle errors and how they write logs. Each area has a long history of costly mistakes, most of them made by developers who built their own mechanisms instead of using proven ones. The overriding rule for managers to enforce is simple: use well-reviewed platform features and libraries for security functions, configure them correctly, and require an expert review before anyone writes a custom alternative.

CISSP-114 covers identity and access management in depth, including authentication factors, federation with SAML, OAuth 2.0 and OpenID Connect, and authorization models. CISSP-112 covers cryptography and key management. Here we look only at how those ideas appear in application code and secure coding standards.

Authentication in applications

Most modern applications should not handle passwords at all. Delegating authentication to a central identity provider through OpenID Connect or SAML gives users single sign-on, lets the organization enforce multifactor authentication and account policies in one place, and removes password storage from the application's attack surface. Ridgeline's merchant dashboard, for example, uses its own identity service for merchants and the corporate identity provider for staff; individual product services never see a password.

When an application must verify passwords itself, the secure coding standard should require the following: accept long passphrases and check new passwords against lists of known compromised passwords rather than imposing arbitrary complexity rules; store only salted hashes made with a slow, purpose-built password hashing function; protect log-in, registration and password reset against automated guessing through rate limiting and lockout or delay; give the same response for an unknown user and a wrong password so that attackers cannot enumerate accounts; and offer or require multifactor authentication, preferably phishing-resistant methods such as passkeys.

Authoritative guidance supports these rules. In Canada, the Canadian Centre for Cyber Security publishes user authentication guidance for information technology systems (ITSP.30.031) that discourages forced periodic changes and arbitrary composition rules. Many Canadian organizations also follow the US NIST Digital Identity Guidelines, whose fourth revision (SP 800-63-4) was finalized in 2025 and requires verifiers to check passwords against compromised lists, to stop requiring composition rules and periodic changes, and to offer phishing-resistant options at higher assurance levels.

scrypt with a random salt per password and stored work parameters; verification uses a constant-time comparison.
import hashlib, hmac, os
# Salted, slow password hashing and constant-time verification (defensive pattern)
def hash_password(password, salt=None, n=2**14, r=8, p=1):
    salt = salt or os.urandom(16)
    digest = hashlib.scrypt(password.encode(), salt=salt, n=n, r=r, p=p, dklen=32)
    return {'alg': 'scrypt', 'n': n, 'r': r, 'p': p, 'salt': salt.hex(), 'hash': digest.hex()}
def verify(password, record):
    salt = bytes.fromhex(record['salt'])
    candidate = hashlib.scrypt(password.encode(), salt=salt, n=record['n'], r=record['r'],
                               p=record['p'], dklen=32)
    return hmac.compare_digest(candidate.hex(), record['hash'])
rec_a = hash_password('correct horse battery staple')
rec_b = hash_password('correct horse battery staple')
print('Same password, different salts -> different hashes:', rec_a['hash'] != rec_b['hash'])
print('Stored fields:', sorted(rec_a))
print('Correct password verifies:', verify('correct horse battery staple', rec_a))
print('Wrong password verifies:  ', verify('correct horse battery stapler', rec_a))
Output
Same password, different salts -> different hashes: True
Stored fields: ['alg', 'hash', 'n', 'p', 'r', 'salt']
Correct password verifies: True
Wrong password verifies:   False

Several details in this example matter. A random salt for every password means identical passwords produce different hashes, so precomputed tables are useless and cracking one hash does not reveal others. A slow, memory-hard function such as scrypt, Argon2 or bcrypt (or PBKDF2 with a high iteration count where approved algorithms are mandated) makes each guess expensive. Storing the work parameters with each hash lets the organization raise them later and rehash passwords at the next log-in. The comparison uses a constant-time function so that response timing reveals nothing about how much of a value matched. Fast general-purpose hashes such as SHA-256 alone, and above all reversible encryption or plain text, are never acceptable for password storage.

Session management

After authentication, a web application usually issues a session identifier or token that the browser presents with each request. Whoever holds that value is, for practical purposes, the user. Session management therefore has to make tokens impossible to guess, hard to steal and short-lived. Tokens must be generated by a cryptographically secure random number generator with enough length that guessing is impossible. A new session identifier must be issued at log-in and whenever privilege changes, to prevent session fixation, in which an attacker plants a known identifier before the victim signs in. Sessions need idle and absolute timeouts and must be invalidated on the server at log-out.

Cookie attributes that protect session identifiers.
Cookie attributeEffectWhy it matters
SecureCookie sent only over HTTPSPrevents exposure on unencrypted connections
HttpOnlyCookie not readable by page scriptsLimits theft of the session through script injection
SameSite=Lax or StrictCookie withheld on most cross-site requestsReduces cross-site request forgery
Path and Domain scoped narrowlyCookie sent only where neededLimits exposure to other applications on related domains
Short Max-Age or session-onlyCookie expiresReduces the window for misuse of a stolen cookie
Tokens come from a cryptographically secure generator; idle and absolute timeouts are enforced on the server.
import secrets, time
# Issuing and checking session tokens (defensive pattern, in-memory store)
IDLE, ABSOLUTE = 15 * 60, 8 * 3600
sessions = {}
def new_session(user, now):
    token = secrets.token_urlsafe(32)          # 256 bits from a CSPRNG
    sessions[token] = {'user': user, 'created': now, 'last': now}
    return token
def check(token, now):
    s = sessions.get(token)
    if not s:
        return 'invalid'
    if now - s['last'] > IDLE or now - s['created'] > ABSOLUTE:
        sessions.pop(token, None)
        return 'expired'
    s['last'] = now
    return 'ok for ' + s['user']
t0 = 1_800_000_000
tok = new_session('merchant-admin-17', t0)
print('token length (chars):', len(tok))
print('after 5 min :', check(tok, t0 + 300))
print('after 25 min idle:', check(tok, t0 + 300 + 20 * 60))
tok2 = new_session('merchant-admin-17', t0)
for m in range(0, 9 * 60, 10):            # active every 10 minutes for 9 hours
    r = check(tok2, t0 + m * 60)
    if r != 'ok for merchant-admin-17':
        print(f'active session ended at {m / 60:.1f} h:', r)
        break
Output
token length (chars): 43
after 5 min : ok for merchant-admin-17
after 25 min idle: expired
active session ended at 8.2 h: expired

Token-based APIs that use JSON Web Tokens (JWTs) raise further issues. The server must verify the signature with an expected algorithm and key, check the issuer, audience and expiry, and never accept unsigned tokens. Because JWTs are often valid until they expire, they should be short-lived, with refresh mechanisms that can be revoked. Sensitive data should not be placed inside a JWT unless it is encrypted, because the contents are only encoded, not hidden.

Cross-site request forgery

Cross-site request forgery (CSRF) tricks a signed-in user's browser into sending an unwanted request to an application, such as a payment or a settings change, by exploiting the fact that browsers automatically attach cookies. Defences are well established: SameSite cookies; anti-CSRF tokens, which are unpredictable values tied to the session and included in each state-changing form or request; checking the Origin header; and requiring re-authentication for high-risk actions such as changing payout bank details. State-changing operations must never use HTTP GET requests. Modern frameworks provide CSRF protection by default, and the coding standard should forbid disabling it without review.

Using cryptography in code

Cryptographic failures appear near the top of every vulnerability ranking, not because algorithms are weak but because developers use them incorrectly. The secure coding standard should require approved, high-level libraries and approved algorithms, and should forbid custom cryptographic designs. Key rules include: use authenticated encryption such as AES-GCM or ChaCha20-Poly1305, never unauthenticated modes such as ECB; never reuse a nonce with the same key; use TLS 1.2 or later, preferably 1.3, with certificate validation left on; obtain random values for keys, tokens and nonces only from a cryptographically secure generator, never from general-purpose functions such as Python's random module; and keep keys out of code, in a key management service or hardware security module, with rotation and access logging. Organizations should also keep an inventory of where cryptography is used, which will be needed for the transition to post-quantum algorithms that CISSP-112 describes.

Worked problem

Reviewing a proposal for custom encryption

A Ridgeline developer proposes encrypting merchants' bank account numbers with a fast home-made scheme that XORs each number with a fixed secret string stored in the configuration file, arguing that standard libraries are slow.

Show solution
  1. Identify the problems: a fixed repeating key can be recovered from a few known values; there is no integrity protection; the key sits in configuration that many people can read.
  2. Apply the standard: use the platform's approved library with AES-GCM, or better, the database's or cloud provider's field-level encryption backed by the key management service.
  3. Consider tokenization: if most services only need to reference an account rather than read it, replace the number with a token and keep the real value in a protected vault service.
  4. Measure performance before optimizing: authenticated encryption with hardware support is fast enough for this workload.

Answer: Reject the custom scheme, use approved authenticated encryption or tokenization with keys in a key management service, and record the decision.

Error handling and failing securely

Errors are inevitable; insecure error handling is not. Two principles apply. First, fail securely, also described as failing closed: when something goes wrong in a security decision, such as an authorization service timing out, the safe outcome is to deny access, not to allow it. Second, reveal little to users while recording much for operators. Error messages shown to users should be generic and helpful ('We could not process your refund. Reference R-4821.') without stack traces, database messages, file paths or software versions, all of which help attackers. The detailed information goes to protected logs, linked by the reference number. The OWASP Top 10 2025 added a category called Mishandling of Exceptional Conditions, reflecting how often poor error handling leads to security failures such as logic bypasses or crashes that expose data.

Logging securely

Applications should log the events needed for security monitoring and investigation: authentication successes and failures, authorization failures, changes to permissions and sensitive settings, high-value transactions and input validation failures. Each entry needs a reliable timestamp, the identity of the actor, the action, the target and the outcome. Logs must not contain passwords, session tokens, full card numbers or other secrets and should minimize personal information. They also need protection against log injection, in which untrusted input containing line breaks or control characters forges or corrupts log entries; structured formats such as JSON and neutralizing control characters address this.

Sensitive fields are redacted and control characters neutralized, so user input cannot forge a new log line.
import json, re
# Structured, sanitized security log entries (defensive pattern)
SENSITIVE = {'password', 'token', 'card_number'}
def clean(v):
    return re.sub(r'[\r\n\t\x00-\x1f]', '_', str(v))[:200]
def log_event(**fields):
    entry = {k: ('[REDACTED]' if k in SENSITIVE else clean(v)) for k, v in fields.items()}
    return json.dumps(entry, sort_keys=True)
print(log_event(ts='2026-10-12T14:03:22Z', event='login_failure', user='ops-lead',
                password='hunter2', src='203.0.113.40'))
print(log_event(ts='2026-10-12T14:05:10Z', event='refund_created', user='M00012345-admin',
                amount='125.50', note='refund for order 77\nERROR fake entry'))
Output
{"event": "login_failure", "password": "[REDACTED]", "src": "203.0.113.40", "ts": "2026-10-12T14:03:22Z", "user": "ops-lead"}
{"amount": "125.50", "event": "refund_created", "note": "refund for order 77_ERROR fake entry", "ts": "2026-10-12T14:05:10Z", "user": "M00012345-admin"}

CISSP-115 and CISSP-116 cover how logs are reviewed, retained and monitored. From a development standpoint, the obligation is to produce logs that are useful, safe and consistent, which is why many organizations provide a shared logging library that applies these rules automatically.

Common mistakes

  • MistakeStoring passwords with a fast hash such as SHA-256 or with reversible encryption.

    InsteadUse a salted, slow password hashing function such as Argon2, scrypt, bcrypt or high-iteration PBKDF2.

  • MistakeComparing secrets with ordinary string equality.

    InsteadUse a constant-time comparison function.

  • MistakeKeeping the same session identifier after log-in.

    InsteadIssue a new identifier at log-in and on privilege change, with idle and absolute timeouts.

  • MistakeGenerating tokens with a general-purpose random function.

    InsteadUse a cryptographically secure random generator.

  • MistakeDesigning a custom encryption scheme for performance.

    InsteadUse approved authenticated encryption from reviewed libraries with keys in a key management service.

  • MistakeAllowing access when the authorization service fails.

    InsteadFail closed and show users a generic message with a reference number.

  • MistakeLogging full request bodies, including passwords and tokens.

    InsteadLog security events in a structured format with secrets redacted and control characters neutralized.

Summary and key terms

  • Use proven platform features and libraries for security functions; custom mechanisms need expert review.
  • Prefer central identity providers; if passwords are handled, check compromised lists, use slow salted hashing and resist automated guessing.
  • Session tokens must be random, protected by cookie attributes, renewed at log-in and limited by timeouts.
  • CSRF is prevented with SameSite cookies, anti-CSRF tokens, Origin checks and re-authentication for high-risk actions.
  • Use authenticated encryption, secure randomness, current TLS and keys held in key management services.
  • Fail closed; show generic errors to users and detailed information only in protected logs.
  • Log security events with reliable context, without secrets, and protect against log injection.
Salt
A random value combined with a password before hashing so identical passwords give different hashes.
Password hashing function
A deliberately slow function such as Argon2, scrypt or bcrypt designed for storing passwords.
Constant-time comparison
A comparison whose duration does not depend on how many characters match.
Session fixation
An attack in which a known session identifier is planted before the victim authenticates.
Cross-site request forgery
Tricking a signed-in user's browser into sending an unwanted request.
Authenticated encryption
Encryption that also protects integrity, such as AES-GCM.
Fail closed
Denying access or stopping safely when a security control fails.
Log injection
Forging or corrupting log entries through untrusted input containing control characters.

Practice problems

Work each problem on paper first, then reveal the answer to check.

  1. Why should most applications delegate authentication to an identity provider?

    Show answer

    It centralizes MFA and policy, enables single sign-on and removes password storage from the application.

  2. Why is a unique salt used for each password?

    Show answer

    So identical passwords hash differently and precomputed tables do not work.

  3. Name two acceptable password hashing functions.

    Show answer

    Argon2, scrypt, bcrypt or high-iteration PBKDF2.

  4. What is the purpose of a constant-time comparison?

    Show answer

    To avoid revealing information through response timing.

  5. What does the HttpOnly cookie attribute do?

    Show answer

    It prevents page scripts from reading the cookie.

  6. How is session fixation prevented?

    Show answer

    By issuing a new session identifier at log-in and on privilege change.

  7. List two defences against CSRF.

    Show answer

    SameSite cookies, anti-CSRF tokens, Origin checks or re-authentication for sensitive actions.

  8. Why is ECB mode unacceptable?

    Show answer

    It does not hide patterns and provides no integrity protection.

  9. What should happen if an authorization service times out?

    Show answer

    The request should be denied (fail closed).

  10. What must never appear in application logs?

    Show answer

    Passwords, session tokens, full card numbers and other secrets.

Lesson quiz · 5 questions

Score 60% or more to complete this lesson.

Take the quiz