CMP-420 · Lesson 7 of 12 · 33 min

Authentication and access control mechanisms

Once a system knows who a user is, it must decide what that user may do. Access control is the mechanism that enforces those decisions on files, records, network services and API endpoints, and broken access control tops the OWASP Top 10 for web applications. This lesson studies the mechanisms that operating systems, databases and applications use: the reference monitor, access control lists and capabilities, Unix permissions, discretionary, mandatory, role-based and attribute-based models, and the session handling that connects a login to every later request.

CISSP-114 covered identity and access management as a management discipline, including identity proofing, federation with SAML and OpenID Connect, multifactor authentication and access reviews. This lesson stays with the enforcement mechanisms that software engineers implement and audit. Authentication factors appear only briefly, because Lessons 4 and 6 already covered password storage and certificates.

Authentication in brief

Authentication establishes identity using something you know (a password or PIN), something you have (a phone, hardware security key or smart card) or something you are (a biometric). Combining factors from different categories, multifactor authentication, defeats most account takeovers that start with a stolen password. The strongest widely available option is phishing-resistant authentication with FIDO2 security keys or passkeys, which use public-key signatures bound to the website's origin, so a look-alike site cannot reuse the response. One-time codes from an authenticator app are a good second choice; SMS codes are better than nothing but can be intercepted or redirected through SIM swapping. Whatever the method, authentication produces a session, and access control then governs every request in that session.

The reference monitor

The classic design principle for enforcement is the reference monitor, described in the 1972 Anderson report for the US Air Force. It is an abstract component that mediates every access by a subject (a user or process) to an object (a file, record or device). A trustworthy reference monitor must be always invoked, so no path bypasses it; tamper-proof, so subjects cannot alter it; and small enough to be verified. Operating system kernels, database engines and well-designed web frameworks all contain one. The commonest flaw in web applications is a missing check: an endpoint or object lookup that bypasses the central authorization code.

A minimal reference monitor that checks and logs every access against an access control list
# A tiny reference monitor: every access goes through one check, and every decision is logged
from dataclasses import dataclass, field
@dataclass
class Monitor:
    acl: dict                                  # object -> {subject: set of rights}
    log: list = field(default_factory=list)
    def check(self, subject, obj, right):
        allowed = right in self.acl.get(obj, {}).get(subject, set())
        self.log.append(f"{'ALLOW' if allowed else 'DENY '} {subject:<6} {right:<5} {obj}")
        return allowed
m = Monitor({
    "grades.csv": {"prof": {"read", "write"}, "ta": {"read"}},
    "syllabus.pdf": {"prof": {"read", "write"}, "ta": {"read", "write"}, "student": {"read"}},
})
for s, o, r in [("ta", "grades.csv", "read"), ("ta", "grades.csv", "write"),
                ("student", "syllabus.pdf", "read"), ("student", "grades.csv", "read"),
                ("guest", "syllabus.pdf", "read")]:
    m.check(s, o, r)
print("\n".join(m.log))
Output
ALLOW ta     read  grades.csv
DENY  ta     write grades.csv
ALLOW student read  syllabus.pdf
DENY  student read  grades.csv
DENY  guest  read  syllabus.pdf

The access matrix: ACLs and capabilities

Butler Lampson's access matrix models every protection state as a table with subjects as rows, objects as columns and rights in the cells. Real systems store the matrix in one of two ways. An access control list (ACL) stores a column with each object: the list of subjects and their rights. A capability list stores a row with each subject: unforgeable tokens, each naming an object and the rights it grants. File systems mostly use ACLs because it is easy to see and change who can access a file. Capabilities make delegation natural and avoid ambient authority, the confused-deputy problem in which a program with broad rights is tricked into using them on someone else's behalf, but revoking them is harder.

The same access matrix stored as ACLs and as capability lists, and what revocation costs
# The same access matrix stored two ways
matrix = {("prof", "grades"): "rw", ("ta", "grades"): "r", ("prof", "notes"): "rw",
          ("ta", "notes"): "rw", ("student", "notes"): "r"}
acl, caps = {}, {}
for (subj, obj), rights in matrix.items():
    acl.setdefault(obj, {})[subj] = rights      # column: stored with the object
    caps.setdefault(subj, {})[obj] = rights     # row: held by the subject
print("ACLs (per object):")
for obj, entries in acl.items(): print(f"  {obj:<7}", entries)
print("Capability lists (per subject):")
for subj, entries in caps.items(): print(f"  {subj:<8}", entries)
# Revoking the TA's access to grades: one edit with ACLs; with capabilities, find every copy the TA holds
del acl["grades"]["ta"]
print("after revocation, grades ACL:", acl["grades"])
Output
ACLs (per object):
  grades  {'prof': 'rw', 'ta': 'r'}
  notes   {'prof': 'rw', 'ta': 'rw', 'student': 'r'}
Capability lists (per subject):
  prof     {'grades': 'rw', 'notes': 'rw'}
  ta       {'grades': 'r', 'notes': 'rw'}
  student  {'notes': 'r'}
after revocation, grades ACL: {'prof': 'rw'}
An access matrix with subjects prof, ta and student as rows and objects grades and notes as columns. The grades column is highlighted and labelled access control list; the ta row is highlighted and labelled capability list.
An access control list is a column of the access matrix; a capability list is a row

Capabilities appear in modern systems more often than the name suggests. A file descriptor in Unix is a capability: once a process has opened a file, it can read it through the descriptor regardless of later permission changes. Bearer tokens and signed URLs on the web are capabilities too, which is why they must be unguessable, short-lived and scoped to the minimum rights.

Unix permissions

Unix and Linux use a compact ACL: each file has an owner, a group and nine permission bits for read, write and execute by the owner, the group and everyone else, usually written in octal. Mode 640 means the owner can read and write, the group can read and others have no access. The umask removes permissions from newly created files, so a process handling secrets can set a umask of 077 to make every new file private. Special bits add power and risk. A setuid program runs with its owner's privileges, so a setuid-root program must be written with extreme care, and modern systems prefer to grant specific Linux capabilities, such as binding to low network ports, instead of full root.

Unix permission bits in symbolic and octal form, and the effect of a restrictive umask
import os, stat, tempfile
d = tempfile.mkdtemp()
path = os.path.join(d, "report.txt")
open(path, "w").write("quarterly numbers\n")
def show(p):
    mode = os.stat(p).st_mode
    return f"{stat.filemode(mode)}  (octal {oct(mode & 0o777)})"
os.chmod(path, 0o644); print("0644:", show(path))
os.chmod(path, 0o640); print("0640:", show(path))
os.chmod(path, 0o600); print("0600:", show(path))
old = os.umask(0o077)                       # new files private by default
p2 = os.path.join(d, "secret.key"); open(p2, "w").write("k")
os.umask(old)
print("file created under umask 077:", show(p2))
Output
0644: -rw-r--r--  (octal 0o644)
0640: -rw-r-----  (octal 0o640)
0600: -rw-------  (octal 0o600)
file created under umask 077: -rw-------  (octal 0o600)

Discretionary and mandatory access control

In discretionary access control (DAC) the owner of an object decides who may access it, as with Unix permissions and most file-sharing tools. DAC is flexible, but it cannot prevent a user, or malware running as that user, from copying sensitive data to a public place. Mandatory access control (MAC) instead enforces a system-wide policy that users cannot override. The Bell–LaPadula model (1973) protects confidentiality with two rules: a subject may not read objects above its clearance (no read up), and may not write to objects below it (no write down), so information cannot leak to lower levels. The Biba model (1977) is its integrity counterpart: no read down and no write up, so low-integrity data cannot contaminate high-integrity objects.

Bell–LaPadula and Biba decisions for the same requests: the rules are mirror images
# Mandatory access control: Bell-LaPadula (confidentiality) and Biba (integrity)
LEVELS = {"unclassified": 0, "protected-a": 1, "protected-b": 2, "secret": 3}
def blp(subject, obj, op):
    s, o = LEVELS[subject], LEVELS[obj]
    if op == "read":  return s >= o           # no read up
    if op == "write": return s <= o           # no write down (star property)
def biba(subject, obj, op):
    s, o = LEVELS[subject], LEVELS[obj]      # here the levels stand for integrity
    if op == "read":  return s <= o           # no read down
    if op == "write": return s >= o           # no write up
cases = [("protected-b", "secret", "read"), ("protected-b", "protected-a", "read"),
         ("protected-b", "protected-a", "write"), ("protected-b", "secret", "write")]
print(f"{'subject':<12} {'object':<12} {'op':<6} {'BLP':<6} Biba")
for s, o, op in cases:
    print(f"{s:<12} {o:<12} {op:<6} {str(blp(s, o, op)):<6} {biba(s, o, op)}")
Output
subject      object       op     BLP    Biba
protected-b  secret       read   False  True
protected-b  protected-a  read   True   False
protected-b  protected-a  write  False  True
protected-b  secret       write  True   False

The labels in the example echo the Government of Canada's security categories. The Policy on Government Security and the Directive on Security Management classify sensitive information as Protected A, B or C according to the injury its compromise could cause to individuals or organizations, and as Confidential, Secret or Top Secret where the injury would be to the national interest. Systems handling Protected B information, the most common category for personal data, must meet specific security controls.

Mandatory controls are not only for governments. SELinux and AppArmor add mandatory policies to Linux, confining what each service may touch even if it is compromised, and mobile operating systems sandbox every app under a mandatory policy. Windows uses integrity levels, a form of Biba, so that a browser tab running at low integrity cannot write to most user files.

Role-based and attribute-based access control

In large organizations, assigning rights person by person does not scale. Role-based access control (RBAC) assigns permissions to roles such as nurse or physician and roles to users, so a new employee gets the right access by being given a role, and separation of duties can be enforced by forbidding certain role combinations. RBAC struggles with context: a nurse should read charts only for patients in their care, at their site, during their shift. Attribute-based access control (ABAC) evaluates policies over attributes of the user, the resource, the action and the environment, such as time, location or an emergency flag. Policies are often written in a dedicated language and evaluated by a separate policy engine, the policy decision point.

RBAC grants the permission; ABAC adds site, care-team, emergency and time-of-day conditions
# Role-based and attribute-based checks for a clinic records system
ROLE_PERMS = {"nurse": {"chart:read", "chart:note"}, "physician": {"chart:read", "chart:note", "chart:order"},
              "clerk": {"schedule:edit"}}
def rbac(user, perm):
    return any(perm in ROLE_PERMS[r] for r in user["roles"])
def abac(user, perm, record, hour):
    if not rbac(user, perm):
        return False, "role lacks permission"
    if record["site"] != user["site"]:
        return False, "different site"
    if record["patient"] not in user["care_team_for"] and not record.get("emergency"):
        return False, "not on the care team"
    if not (7 <= hour < 19) and "on-call" not in user["roles"]:
        return False, "outside shift hours"
    return True, "allowed"
nina = {"roles": ["nurse"], "site": "north", "care_team_for": {"P-104"}}
rec = {"patient": "P-104", "site": "north"}
for perm, r, h in [("chart:read", rec, 10), ("chart:order", rec, 10),
                   ("chart:read", {"patient": "P-222", "site": "north"}, 10),
                   ("chart:read", {"patient": "P-222", "site": "north", "emergency": True}, 10),
                   ("chart:read", rec, 23)]:
    ok, why = abac(nina, perm, r, h)
    flag = " (emergency)" if r.get("emergency") else ""
    print(f"{perm:<12} patient {r['patient']} at {h:02d}:00{flag:<12} -> {why}")
Output
chart:read   patient P-104 at 10:00             -> allowed
chart:order  patient P-104 at 10:00             -> role lacks permission
chart:read   patient P-222 at 10:00             -> not on the care team
chart:read   patient P-222 at 10:00 (emergency) -> allowed
chart:read   patient P-104 at 23:00             -> outside shift hours

Sessions: connecting authentication to every request

HTTP is stateless, so after login a web application must recognize the same user on each later request. It does so with a session identifier in a cookie or a token in a header. Session handling is security-critical. Identifiers must be long and random, generated by a cryptographic generator such as Python's secrets module; servers can store only a hash of each identifier, so a stolen session table cannot be replayed. A new identifier must be issued at login, so an attacker cannot plant a known identifier beforehand. Sessions need both an idle timeout and an absolute lifetime, and logout must invalidate the session on the server, not just delete the browser's cookie. Cookies carrying sessions should be marked Secure, HttpOnly and SameSite.

Random session identifiers stored as hashes, with idle and absolute timeouts
import secrets, hashlib, hmac, time
# Server-side sessions: random identifiers, stored hashed, with idle and absolute timeouts
STORE = {}
IDLE, ABSOLUTE = 15 * 60, 8 * 3600
def create(user, now):
    sid = secrets.token_urlsafe(32)                      # 256 bits of randomness
    STORE[hashlib.sha256(sid.encode()).hexdigest()] = {"user": user, "created": now, "seen": now}
    return sid
def lookup(sid, now):
    key = hashlib.sha256(sid.encode()).hexdigest()
    s = STORE.get(key)
    if not s: return None, "unknown session"
    if now - s["seen"] > IDLE: return None, "idle timeout"
    if now - s["created"] > ABSOLUTE: return None, "absolute timeout"
    s["seen"] = now
    return s["user"], "ok"
t0 = 1_800_000_000
sid = create("ana", t0)
print("session id length:", len(sid), "characters (value not printed)")
for label, t in [("after 5 min", t0 + 300), ("after 25 min idle", t0 + 1800)]:
    print(f"{label:<18}", lookup(sid, t))
sid2 = create("ana", t0)
for k in range(1, 40):                                    # active every 14 minutes all day
    user, why = lookup(sid2, t0 + k * 14 * 60)
    if why != "ok": print(f"active session ended after {k * 14 / 60:.1f} h:", why); break
Output
session id length: 43 characters (value not printed)
after 5 min        ('ana', 'ok')
after 25 min idle  (None, 'idle timeout')
active session ended after 8.2 h: absolute timeout

Self-contained tokens such as JSON Web Tokens (JWTs) avoid a server-side store by carrying signed claims, but that makes them hard to revoke before expiry. If you use them, verify the signature with an explicitly configured algorithm, check the issuer, audience and expiry, keep lifetimes short, and use a server-side revocation or refresh mechanism.

Worked examples

Worked problem

Fixing an object-level access check

A course portal serves /api/submissions/<id>. The handler checks that the user is logged in, then loads the submission by id and returns it. What is wrong, and where should the fix go?

Show solution
  1. Authentication is checked but authorization is not: any logged-in student can read any submission by changing the id.
  2. The check belongs in the data-access layer: load the submission only if its owner is the current user, or the user is an instructor of that course.
  3. Return 404 rather than 403 for records the user may not see, so ids of other users' records are not confirmed, and log the denied access.
  4. Add an automated test that requests another user's submission and expects denial.

Answer: Enforce object-level authorization on every lookup, centrally, and test for it; authentication alone is not enough.

Worked problem

Choosing permissions for a secrets file

A service running as user app in group app reads an API key from /etc/app/key. Operators in group ops must be able to rotate it. Which ownership and mode should the file have?

Show solution
  1. The service needs read access only; operators need write access to rotate.
  2. Make the file owned by root:app with mode 0640 so the service can read it through the group, and give ops a controlled rotation tool via sudo rather than group write access.
  3. Others get no access, and the directory /etc/app should be 0750 so the file name is not even listed to other users.

Answer: Owner root, group app, mode 0640, directory 0750, with rotation through a controlled privileged tool.

Worked problem

Bell–LaPadula in practice

A user cleared for Protected B wants to paste a paragraph from a Protected B document into an Unclassified newsletter. What does the Bell–LaPadula model say, and how do real systems handle the need?

Show solution
  1. Writing from a Protected B session into an Unclassified object is a write down, forbidden by the star property.
  2. The rule prevents accidental or malicious leakage, including by malware running in the user's session.
  3. Real systems handle legitimate needs through a trusted declassification process in which an authorized reviewer releases specific content.

Answer: It is a forbidden write down; release requires a separate, reviewed declassification step.

Common mistakes

  • MistakeIf a user is logged in, they can be trusted to access what they request.

    InsteadCheck authorization for every object and action, not just authentication.

  • MistakeHiding a link or button is enough to protect an admin page.

    InsteadEnforce access on the server for every request; hidden interfaces are trivially found.

  • MistakeSession identifiers can be sequential if they are long.

    InsteadGenerate identifiers with a cryptographic random generator so they cannot be predicted.

  • MistakeDeleting the session cookie logs the user out.

    InsteadInvalidate the session on the server; a copied cookie would otherwise keep working.

  • MistakeRunning a service as root is simpler and safe if the code is good.

    InsteadApply least privilege: a dedicated user, minimal capabilities and a mandatory policy limit the damage of any bug.

Summary and key terms

  • A reference monitor must mediate every access, resist tampering and be small enough to verify.
  • ACLs store the access matrix by object; capabilities store it by subject and make delegation easy but revocation hard.
  • Unix permission bits and the umask implement a compact discretionary ACL; setuid programs need extreme care.
  • Bell–LaPadula protects confidentiality (no read up, no write down); Biba protects integrity (no read down, no write up).
  • RBAC scales permissions through roles; ABAC adds context through attributes and policies.
  • Sessions need random identifiers, regeneration at login, idle and absolute timeouts and server-side logout.
  • Broken access control, usually a missing object-level check, is the most common serious web flaw.
Reference monitor
The component that mediates every access by a subject to an object.
Access matrix
A model of protection state with subjects as rows, objects as columns and rights in cells.
Access control list
A list stored with an object naming who may access it and how.
Capability
An unforgeable token that grants its holder specific rights to an object.
Confused deputy
A privileged program tricked into misusing its authority on behalf of another party.
Discretionary access control
Access decided by an object's owner.
Mandatory access control
Access governed by a system-wide policy users cannot override.
Star property
The Bell–LaPadula rule forbidding writes to lower classification levels.
Role-based access control
Granting permissions to roles and roles to users.
Attribute-based access control
Granting access by evaluating policies over user, resource, action and environment attributes.

Practice problems

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

  1. List the three properties of a reference monitor.

    Show answer

    Always invoked, tamper-proof, and small enough to verify.

  2. Which storage of the access matrix makes revocation easy?

    Show answer

    Access control lists, because rights are stored with the object.

  3. What does Unix mode 0750 mean for a directory?

    Show answer

    Owner read, write and enter; group read and enter; others nothing.

  4. What is the effect of umask 077 on new files?

    Show answer

    Group and others get no permissions; only the owner has access.

  5. State the two Bell–LaPadula rules.

    Show answer

    No read up (simple security property) and no write down (star property).

  6. Why does Biba forbid reading down?

    Show answer

    To stop low-integrity data from influencing high-integrity subjects.

  7. Give one weakness of RBAC that ABAC addresses.

    Show answer

    RBAC cannot express context such as time, location or care relationship.

  8. Why should a new session identifier be issued at login?

    Show answer

    To prevent session fixation, where an attacker plants a known identifier before the user logs in.

  9. What cookie attributes should a session cookie have?

    Show answer

    Secure, HttpOnly and SameSite (Lax or Strict).

  10. Why are bearer tokens a form of capability?

    Show answer

    Whoever holds the token can use the rights it grants, regardless of identity.

Lesson quiz · 5 questions

Score 60% or more to complete this lesson.

Take the quiz