Skip to content

Authentication, Authorization & Encryption

Authentication and authorization are the gatekeepers of every application. Authentication answers “who are you?” while authorization answers “what are you allowed to do?” Encryption is the underlying technology that makes both possible in a hostile network environment. Getting any of these wrong can turn a single vulnerability into a full system compromise.


Authentication vs Authorization

These two concepts are frequently confused but are fundamentally different:

AspectAuthentication (AuthN)Authorization (AuthZ)
QuestionWho are you?What can you do?
WhenAt login / session startOn every protected action
MechanismPasswords, tokens, biometrics, MFARoles, permissions, policies
Failure response401 Unauthorized403 Forbidden
ExampleLogging in with email and passwordAn editor can publish posts but cannot delete users
┌──────────┐ ┌──────────────────┐ ┌───────────────────┐
│ │ │ │ │ │
│ User │─────►│ Authentication │─────►│ Authorization │
│ │ │ "Who are you?" │ │ "What can you │
│ │ │ │ │ access?" │
└──────────┘ └──────────────────┘ └───────────────────┘
│ │
401 if fail 403 if fail

Critical rule: Authentication must always come before authorization. You cannot determine what someone is allowed to do until you know who they are.


Password Storage

Never store passwords in plain text. Ever. This is the single most important rule in authentication security. If your database is breached, every user’s password is immediately compromised.

The Evolution of Password Storage

MethodSecurityWhy
Plain textNoneAttacker reads passwords directly from the database
MD5 / SHA-1 hashVery weakPre-computed rainbow tables crack most passwords instantly
SHA-256 hashWeakStill vulnerable to rainbow tables and GPU-accelerated brute force
SHA-256 + saltModerateUnique salt prevents rainbow tables, but SHA-256 is too fast for passwords
bcryptStrongIntentionally slow, includes salt, configurable cost factor
Argon2idStrongestMemory-hard, resistant to GPU/ASIC attacks, recommended for new systems
scryptStrongMemory-hard, good alternative to Argon2

Why Speed Is the Enemy

General-purpose hash functions like SHA-256 are designed to be fast. A modern GPU can compute billions of SHA-256 hashes per second. Password hashing algorithms like bcrypt and Argon2 are intentionally slow — they make brute force attacks computationally infeasible.

SHA-256: ~10,000,000,000 hashes/second (GPU) = crack in seconds
bcrypt: ~10,000 hashes/second (GPU) = crack in years
Argon2id: ~1,000 hashes/second (GPU) = crack in centuries

Password Hashing in Practice

# Using bcrypt
import bcrypt
def hash_password(password: str) -> str:
"""Hash a password with bcrypt (auto-generates salt)."""
salt = bcrypt.gensalt(rounds=12) # Cost factor of 12 (~250ms per hash)
hashed = bcrypt.hashpw(password.encode("utf-8"), salt)
return hashed.decode("utf-8")
def verify_password(password: str, hashed: str) -> bool:
"""Verify a password against a bcrypt hash."""
return bcrypt.checkpw(
password.encode("utf-8"),
hashed.encode("utf-8")
)
# Using Argon2 (recommended for new projects)
from argon2 import PasswordHasher
ph = PasswordHasher(
time_cost=3, # Number of iterations
memory_cost=65536, # Memory usage in KiB (64 MB)
parallelism=4, # Number of parallel threads
)
def hash_password_argon2(password: str) -> str:
"""Hash a password with Argon2id."""
return ph.hash(password)
def verify_password_argon2(password: str, hashed: str) -> bool:
"""Verify a password against an Argon2id hash."""
try:
return ph.verify(hashed, password)
except Exception:
return False
# Usage
hashed = hash_password("my_secure_password_123")
print(hashed) # $2b$12$LJ3m4ys3Lg...
assert verify_password("my_secure_password_123", hashed) is True
assert verify_password("wrong_password", hashed) is False

Session-Based vs Token-Based Authentication

There are two fundamental approaches to maintaining user authentication state after login.

Session-Based Authentication

The server creates a session and stores it server-side. The client receives a session ID stored in a cookie.

1. User logs in with credentials
Client ──────────────────────────► Server
POST /login
{ email, password }
2. Server creates session, returns session ID in cookie
Client ◄────────────────────────── Server
Set-Cookie: sessionId=abc123
(Session stored in memory/Redis/DB)
3. Subsequent requests include the cookie automatically
Client ──────────────────────────► Server
Cookie: sessionId=abc123
(Server looks up session to identify user)

Pros: Easy to revoke (delete server-side session), simple to implement, cookies sent automatically. Cons: Requires server-side storage, harder to scale across multiple servers (use Redis for shared sessions), vulnerable to CSRF if not mitigated.

Token-Based Authentication (JWT)

The server creates a signed token containing user claims. The client stores it and sends it with each request. No server-side state is needed.

1. User logs in with credentials
Client ──────────────────────────► Server
POST /login
{ email, password }
2. Server creates and signs a JWT, returns it
Client ◄────────────────────────── Server
{ "token": "eyJhbGciOiJI..." }
3. Subsequent requests include the token in the Authorization header
Client ──────────────────────────► Server
Authorization: Bearer eyJhbGciOiJI...
(Server verifies signature, extracts claims)

Pros: Stateless (no server-side storage), scales easily, works across domains and services. Cons: Cannot be revoked easily (use short expiration + refresh tokens), larger than session IDs, must be stored securely on the client.


JSON Web Tokens (JWT) Deep Dive

A JWT consists of three Base64URL-encoded parts separated by dots:

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U
│ │ │
│ Header │ Payload │ Signature
│ {"alg":"HS256"} │ {"sub":"1234567890", │ HMACSHA256(
│ │ "name":"John", │ base64(header) + "." +
│ │ "iat":1516239022} │ base64(payload),
│ │ │ secret
│ │ │ )

JWT Implementation

import jwt
from datetime import datetime, timedelta, timezone
SECRET_KEY = "your-256-bit-secret" # In production, load from secrets manager
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 15
REFRESH_TOKEN_EXPIRE_DAYS = 7
def create_access_token(user_id: str, role: str) -> str:
"""Create a short-lived access token."""
payload = {
"sub": user_id,
"role": role,
"type": "access",
"iat": datetime.now(timezone.utc),
"exp": datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES),
}
return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
def create_refresh_token(user_id: str) -> str:
"""Create a longer-lived refresh token."""
payload = {
"sub": user_id,
"type": "refresh",
"iat": datetime.now(timezone.utc),
"exp": datetime.now(timezone.utc) + timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS),
}
return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
def verify_token(token: str) -> dict:
"""Verify and decode a JWT token."""
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
return payload
except jwt.ExpiredSignatureError:
raise ValueError("Token has expired")
except jwt.InvalidTokenError:
raise ValueError("Invalid token")
# Login endpoint
@app.route("/login", methods=["POST"])
def login():
email = request.json.get("email")
password = request.json.get("password")
user = db.query(User).filter_by(email=email).first()
if not user or not verify_password(password, user.password_hash):
# Generic message prevents username enumeration
abort(401, description="Invalid email or password")
access_token = create_access_token(str(user.id), user.role)
refresh_token = create_refresh_token(str(user.id))
return jsonify({
"access_token": access_token,
"refresh_token": refresh_token,
"token_type": "bearer",
})

Common JWT Pitfalls

PitfallRiskFix
Setting alg to noneAttacker bypasses signature verification entirelyAlways validate the algorithm on the server, reject none
Storing JWT in localStorageVulnerable to XSS — any script can read itStore in HttpOnly, Secure, SameSite cookies
No expiration (exp)Token is valid forever if leakedAlways set short expiration (15 min for access tokens)
Sensitive data in payloadJWT payload is Base64-encoded, NOT encrypted — anyone can read itNever put passwords, SSNs, or secrets in the payload
Using a weak secretAttacker can brute force the HMAC keyUse at least 256 bits of cryptographic randomness
Not validating iss and audToken from one service accepted by anotherValidate issuer and audience claims
Confusing signing with encryptionJWT is signed (integrity) but NOT encrypted (confidentiality)Use JWE if payload must be confidential

OAuth 2.0

OAuth 2.0 is an authorization framework (not an authentication protocol) that allows third-party applications to access user resources without exposing credentials.

Key Roles

RoleDescriptionExample
Resource OwnerThe user who owns the dataA GitHub user
ClientThe application requesting accessA third-party CI tool
Authorization ServerIssues tokens after authenticating the userGitHub’s OAuth server
Resource ServerHosts the protected resourcesGitHub’s API

Authorization Code Flow with PKCE

This is the recommended flow for all clients, including single-page applications and mobile apps. PKCE (Proof Key for Code Exchange) prevents authorization code interception attacks.

┌──────────┐ ┌───────────────┐
│ │ 1. Redirect to auth server │ │
│ Client │─────────────────────────────►│ Authorization │
│ (App) │ + code_challenge (PKCE) │ Server │
│ │ │ │
│ │ 2. User logs in & consents │ │
│ │ │ │
│ │ 3. Redirect back with code │ │
│ │◄─────────────────────────────│ │
│ │ ?code=abc123 │ │
│ │ │ │
│ │ 4. Exchange code for tokens │ │
│ │─────────────────────────────►│ │
│ │ + code_verifier (PKCE) │ │
│ │ │ │
│ │ 5. Receive access token │ │
│ │◄─────────────────────────────│ │
│ │ + refresh token │ │
└──────────┘ └───────────────┘
│ 6. Use access token to call API
┌───────────────┐
│ Resource │
│ Server │
│ (API) │
└───────────────┘

Client Credentials Flow

Used for service-to-service communication where no user is involved.

┌──────────┐ ┌───────────────┐
│ │ 1. Send client_id + secret │ │
│ Service │─────────────────────────────►│ Authorization │
│ (App) │ │ Server │
│ │ 2. Receive access token │ │
│ │◄─────────────────────────────│ │
└──────────┘ └───────────────┘

This flow is simpler but should only be used for trusted backend services, never for client-side applications.

OAuth Best Practices

  • Always use PKCE — even for confidential clients, it adds defense in depth
  • Use short-lived access tokens (5-15 minutes) with refresh tokens
  • Store client secrets securely — never in client-side code or version control
  • Validate the state parameter to prevent CSRF attacks during the OAuth flow
  • Restrict scopes to the minimum required — do not request admin when read:user suffices
  • Use openid scope with OIDC if you need authentication (identity), not just authorization (access)

Multi-Factor Authentication (MFA)

MFA requires users to provide two or more verification factors from different categories:

Factor TypeDescriptionExamples
Something you knowSecret informationPassword, PIN, security questions
Something you havePhysical possessionPhone (TOTP), hardware key (YubiKey), smart card
Something you areBiometric traitFingerprint, face recognition, iris scan

MFA Methods Ranked by Security

MethodSecurity LevelUsabilityNotes
SMS codesLowHighVulnerable to SIM swapping and SS7 attacks
Email codesLowHighIf email is compromised, MFA is bypassed
TOTP (Google Authenticator)MediumMediumTime-based codes, device-dependent
Push notificationsMediumHighConvenient but vulnerable to fatigue attacks
Hardware keys (FIDO2/WebAuthn)HighestMediumPhishing-resistant, cryptographic proof

Recommendation: Implement TOTP as the baseline and encourage hardware keys for high-privilege accounts. Avoid SMS-based MFA for any sensitive system.


Authorization Patterns

Once a user is authenticated, the next question is: what are they allowed to do?

Role-Based Access Control (RBAC)

Permissions are assigned to roles, and users are assigned to roles. This is the most common authorization model.

┌───────────┐ ┌───────────┐ ┌──────────────┐
│ Users │─────►│ Roles │─────►│ Permissions │
└───────────┘ └───────────┘ └──────────────┘
Alice ─────► Admin ─────► create, read, update, delete, manage_users
Bob ─────► Editor ─────► create, read, update
Carol ─────► Viewer ─────► read

Pros: Simple to understand and implement, maps well to organizational hierarchies. Cons: Can lead to role explosion (too many fine-grained roles), not context-aware.

Attribute-Based Access Control (ABAC)

Access decisions are based on attributes of the user, the resource, the action, and the environment.

Policy: "Allow if user.department == resource.department
AND user.clearance >= resource.classification
AND environment.time is within business_hours"

Pros: Extremely flexible and context-aware, handles complex policies. Cons: More complex to implement and audit, harder to reason about.

Access Control Lists (ACL)

Each resource has a list specifying which users or groups have which permissions on that specific resource.

Document: "Q4 Report"
├── Alice: read, write
├── Bob: read
├── Finance Group: read, write
└── Everyone else: no access

Pros: Fine-grained per-resource control, familiar model (file system permissions). Cons: Does not scale well for large numbers of resources, hard to audit globally.

Choosing an Authorization Model

ScenarioRecommended Model
Simple web app with clear user rolesRBAC
Enterprise with complex policiesABAC
File sharing / document managementACL
Microservices with cross-service authRBAC + ABAC hybrid

Encryption Fundamentals

Encryption transforms readable data (plaintext) into unreadable data (ciphertext) using an algorithm and a key. Only those with the correct key can reverse the process.

Symmetric Encryption

The same key is used for both encryption and decryption.

Plaintext ──► [ AES-256 + Key ] ──► Ciphertext
Ciphertext ──► [ AES-256 + Key ] ──► Plaintext
AlgorithmKey SizeUse Case
AES-256-GCM256 bitsData at rest, file encryption, database fields (recommended)
AES-128128 bitsAcceptable where AES-256 performance is a concern
ChaCha20-Poly1305256 bitsMobile and IoT (faster in software than AES without hardware support)

Key challenge: How do you securely share the key between parties?

Asymmetric Encryption

Uses a key pair: a public key (shared freely) for encryption and a private key (kept secret) for decryption.

Plaintext ──► [ RSA/ECC + Public Key ] ──► Ciphertext
Ciphertext ──► [ RSA/ECC + Private Key ] ──► Plaintext
AlgorithmKey SizeUse Case
RSA2048-4096 bitsTLS certificates, digital signatures, key exchange
ECC (Elliptic Curve)256-384 bitsTLS, mobile apps (smaller keys = faster, same security)
Ed25519256 bitsSSH keys, digital signatures (fast, secure)

Key insight: Asymmetric encryption solves the key distribution problem but is ~1000x slower than symmetric encryption. In practice, systems use asymmetric encryption to exchange a symmetric key, then use the symmetric key for bulk data encryption (this is how TLS works).

When to Use Which

ScenarioEncryption TypeSpecific Algorithm
Encrypting data at rest (database fields)SymmetricAES-256-GCM
Encrypting files on diskSymmetricAES-256-GCM or ChaCha20
TLS/HTTPS (data in transit)Both (hybrid)ECDHE + AES-256-GCM
Digital signaturesAsymmetricRSA-2048+ or Ed25519
SSH authenticationAsymmetricEd25519

Hashing

Hashing is a one-way function that converts input into a fixed-size output (hash/digest). Unlike encryption, hashing cannot be reversed.

Input ──► [ Hash Function ] ──► Fixed-size digest
"hello" ──► [ SHA-256 ] ──► 2cf24dba5fb0a30e... (64 hex chars, always)

Properties of Cryptographic Hashes

PropertyDescription
DeterministicSame input always produces the same output
Fixed output sizeRegardless of input size, output is always the same length
Avalanche effectA tiny change in input produces a completely different hash
Pre-image resistanceGiven a hash, it is computationally infeasible to find the original input
Collision resistanceIt is infeasible to find two different inputs that produce the same hash

Common Hash Functions and Their Uses

FunctionOutput SizeUse CaseNotes
SHA-256256 bitsData integrity, digital signatures, checksumsGeneral-purpose, widely used
SHA-3256/512 bitsSame as SHA-256, newer standardAlternative design to SHA-2 family
BLAKE3256 bitsFile hashing, checksumsVery fast, modern design
bcrypt184 bitsPassword hashingIntentionally slow, built-in salt
Argon2idConfigurablePassword hashingMemory-hard, recommended for passwords
MD5128 bitsLegacy checksums onlyBroken — never use for security
SHA-1160 bitsLegacy onlyBroken — never use for security

TLS/SSL and Certificate Management

TLS (Transport Layer Security) encrypts data in transit between clients and servers. SSL is the deprecated predecessor — when people say “SSL,” they almost always mean TLS.

The TLS Handshake (Simplified)

Client Server
│ │
│ 1. ClientHello │
│ (supported ciphers, TLS version) │
│──────────────────────────────────────►│
│ │
│ 2. ServerHello + Certificate │
│ (chosen cipher, server's public key) │
│◄──────────────────────────────────────│
│ │
│ 3. Client verifies certificate │
│ against trusted CA │
│ │
│ 4. Key Exchange │
│ (both derive shared secret) │
│──────────────────────────────────────►│
│ │
│ 5. Encrypted communication begins │
│◄─────────────────────────────────────►│
│ (using symmetric key derived above) │

Certificate Management Best Practices

PracticeWhy
Use certificates from trusted CAs (Let’s Encrypt, DigiCert)Self-signed certs trigger browser warnings and break trust chains
Automate certificate renewalExpired certs cause outages — use certbot or cloud-managed certificates
Use TLS 1.2 as the minimum versionTLS 1.0 and 1.1 have known vulnerabilities and are deprecated
Prefer TLS 1.3Faster handshake (1-RTT), stronger security, no legacy cipher suites
Enable HSTSPrevent protocol downgrade attacks (HTTPS to HTTP)
Use strong cipher suitesPrefer ECDHE for key exchange, AES-GCM or ChaCha20 for encryption
Implement certificate pinning (mobile)Prevents MITM attacks using rogue certificates

Quick Reference: Auth & Crypto Decision Guide

DecisionRecommendation
Password hashingArgon2id (new projects) or bcrypt (mature ecosystem support)
Session managementSession cookies for traditional web apps, JWT for APIs and SPAs
JWT storage (browser)HttpOnly, Secure, SameSite=Strict cookie — never localStorage
JWT expirationAccess token: 15 minutes. Refresh token: 7 days.
OAuth flowAuthorization Code + PKCE for all clients
MFA methodTOTP baseline, FIDO2/WebAuthn for high-value accounts
Authorization modelRBAC for most apps, ABAC for complex enterprise policies
Data at rest encryptionAES-256-GCM
Data in transitTLS 1.2+ with strong cipher suites
Digital signaturesEd25519 or RSA-2048+
Key managementNever hardcode — use Vault, AWS KMS, or cloud-native secrets managers

Next Steps