CMPUT-331 · 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 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))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 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"])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'}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.
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))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.
# 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)}")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.
# 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}")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.
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); breaksession 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 timeoutSelf-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
- Authentication is checked but authorization is not: any logged-in student can read any submission by changing the id.
- 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.
- 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.
- 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
- The service needs read access only; operators need write access to rotate.
- 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.
- 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
- Writing from a Protected B session into an Unclassified object is a write down, forbidden by the star property.
- The rule prevents accidental or malicious leakage, including by malware running in the user's session.
- 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.
List the three properties of a reference monitor.
Show answer
Always invoked, tamper-proof, and small enough to verify.
Which storage of the access matrix makes revocation easy?
Show answer
Access control lists, because rights are stored with the object.
What does Unix mode 0750 mean for a directory?
Show answer
Owner read, write and enter; group read and enter; others nothing.
What is the effect of umask 077 on new files?
Show answer
Group and others get no permissions; only the owner has access.
State the two Bell–LaPadula rules.
Show answer
No read up (simple security property) and no write down (star property).
Why does Biba forbid reading down?
Show answer
To stop low-integrity data from influencing high-integrity subjects.
Give one weakness of RBAC that ABAC addresses.
Show answer
RBAC cannot express context such as time, location or care relationship.
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.
What cookie attributes should a session cookie have?
Show answer
Secure, HttpOnly and SameSite (Lax or Strict).
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.
Create a free account to save your progress, take quizzes and earn a certificate.