Security is not a feature that is "added at the end": it is a cross-cutting property that must be present from the very first line of the design. This is what security by design means. A system that works, scales, and is always available is worthless if an attacker can steal users' data or impersonate their identity. In this lesson you will learn the fundamental principles of security (defense in depth and least privilege), the essential difference between authentication and authorization, the modern protocols that implement them (OAuth2, OIDC, and JWT), the most frequent vulnerabilities according to OWASP, and finally how to protect data through encryption in transit and at rest.

Professional note: the content of this lesson is educational about architecture. Any real security implementation in a production system must be reviewed by security specialists and comply with applicable regulations (for example GDPR/LOPDGDD).

Contents

  1. Principles: defense in depth and least privilege
  2. Authentication vs. Authorization
  3. OAuth2, OpenID Connect, and JWT
  4. OWASP Top 10 summarized
  5. Encryption in transit and at rest
  6. Common mistakes and tips
  7. Exercises

  1. Principles: defense in depth and least privilege

Two principles underpin any secure architecture.

Defense in depth consists of not relying on a single barrier, but placing multiple independent layers of security. If one fails, the others keep protecting. It is the principle of the medieval castle: moat, wall, gates, towers.

graph LR
    A[Internet] --> F[Firewall / WAF]
    F --> G[API Gateway + Auth]
    G --> S[Validation in the service]
    S --> D[(DB with minimal permissions)]

Each layer of the diagram protects independently: even if an attacker gets through the firewall, they still have to get past the gateway's authentication, the service's validation, and the database's permissions.

Least privilege establishes that each user, service, or process should have only the permissions strictly necessary for its function, not one more. A service account that only reads reports must not be able to drop tables.

-- BAD: the application uses an all-powerful user
GRANT ALL PRIVILEGES ON *.* TO 'app'@'%';

-- GOOD: only the permissions it really needs, on the specific database
GRANT SELECT, INSERT, UPDATE ON store.* TO 'app'@'10.0.%';

In the "BAD" example, if the application is compromised, the attacker has total control of all databases. In the "GOOD" one, they can only read from and write to store, cannot even delete (DELETE), and only from the internal network 10.0.%. The potential damage is contained.

  1. Authentication vs. Authorization

These two concepts are constantly confused, but they are distinct:

Authentication (AuthN) Authorization (AuthZ)
Question Who are you? What can you do?
Verifies Identity Permissions
Occurs First After (already identified)
Example Log in with username and password "Can this user delete invoices?"
Typical result An identity token Allow or deny the operation

The mnemonic: AuthN = authenticNation (who you are); AuthZ = authoriZation (what you can do). You always authenticate first and authorize afterward.

The most common authorization models are:

  • RBAC (Role-Based Access Control): permissions are assigned to roles (admin, editor, reader) and users are given roles. Simple and very widespread.
  • ABAC (Attribute-Based Access Control): the decision is based on attributes (department, time, location, seniority). More flexible and granular, but more complex.

  1. OAuth2, OpenID Connect, and JWT

These three terms are often mixed up. The key to not getting confused:

Technology What it is for Answers
OAuth 2.0 Delegated authorization (granting access to resources) What can this app do on your behalf?
OpenID Connect (OIDC) Authentication (layer on top of OAuth2) Who is the user?
JWT Token format (carries the info) (Not a protocol, it's a container)

OAuth 2.0 allows an application to access resources on the user's behalf without knowing their password (for example, "Log in with Google"). OIDC is built on top of OAuth2 and adds the piece it was missing: identifying the user, by means of an ID Token. JWT (JSON Web Token) is the most common format in which these tokens travel.

A JWT has three parts separated by dots: header.payload.signature.

// Header: algorithm and type
{ "alg": "RS256", "typ": "JWT" }

// Payload (claims): the information, NOT encrypted, only Base64-encoded
{
  "sub": "user-123",           // subject: user identifier
  "name": "Ana García",
  "roles": ["editor"],          // used for authorization
  "iat": 1718000000,            // issued at: when it was issued
  "exp": 1718003600             // expiration: when it expires (1 hour later)
}

A critical point that confuses beginners: the payload of a JWT is not encrypted, only Base64-encoded. Anyone can read it. What the signature (the third part) guarantees is integrity and authenticity: that no one has modified the content and that it was issued by whoever it claims to be. That is why you must never put secrets in a JWT.

// Validate a JWT signed with RS256 (public/private key)
Claims claims = Jwts.parserBuilder()
    .setSigningKey(publicKey)      // We verify the signature with the issuer's public key
    .build()
    .parseClaimsJws(receivedToken) // Throws an exception if the signature or expiration is invalid
    .getBody();

String userId = claims.getSubject();          // We extract the "sub"
List<String> roles = claims.get("roles", List.class);  // and the roles to authorize

This code verifies that the token has not been tampered with (thanks to the signature) or expired (exp), and only then extracts the identifier and the roles. If validation fails, it throws an exception and the request is rejected.

  1. OWASP Top 10 summarized

OWASP (Open Worldwide Application Security Project) periodically publishes the Top 10 most critical web security risks. It is required reading. Summary of the main categories:

# Category What it consists of Key mitigation
A01 Broken access control Accessing resources without permission Server-side authorization, deny by default
A02 Cryptographic failures Sensitive data poorly protected Encrypt in transit and at rest, don't invent cryptography
A03 Injection (SQL, etc.) Malicious data treated as code Parameterized queries, validate inputs
A04 Insecure design Design flaws, not code flaws Threat modeling from the design stage
A05 Security misconfiguration Default values, exposed errors Hardening, review configuration
A06 Vulnerable components Outdated libraries with CVEs Update, scan dependencies
A07 Authentication failures Weak sessions, brute force MFA, attempt limits, secure session management
A08 Integrity failures Unverified data/code Signatures, verify the supply chain
A09 Logging/monitoring failures Not detecting attacks Security logs, alerts, observability
A10 SSRF Forcing the server to request internal URLs Validate and restrict request destinations

The classic example is SQL injection (A03):

// BAD: concatenating the user's input directly into the query
String sql = "SELECT * FROM users WHERE email = '" + email + "'";
// If email = "' OR '1'='1", the query returns ALL users.

// GOOD: parameterized query; the engine treats "email" as data, never as code
PreparedStatement ps = conn.prepareStatement(
    "SELECT * FROM users WHERE email = ?");
ps.setString(1, email);   // The driver escapes the value safely

In the "BAD" version, an attacker can inject SQL into the email field and manipulate the query. In the "GOOD" one, the ? is a placeholder: the engine never interprets the user's value as SQL instructions, eliminating the vulnerability at its root.

  1. Encryption in transit and at rest

Data must be protected in its two states:

  • In transit: while it travels over the network. It is protected with TLS (the padlock of HTTPS). It prevents someone who intercepts the traffic from reading it (man in the middle).
  • At rest: while it is stored on disk, in a database, or in backups. It is protected by encrypting the storage or specific columns.
In transit At rest
Threat Interception on the network Theft of the disk or backup
Technology TLS / HTTPS Disk/column encryption (AES)
Example https:// connection Database with transparent encryption (TDE)
# Force HTTPS and redirect all HTTP traffic in Nginx
server {
    listen 80;
    return 301 https://$host$request_uri;   # Any HTTP request is redirected to HTTPS
}
server {
    listen 443 ssl;
    ssl_protocols TLSv1.2 TLSv1.3;          # Only modern, secure versions of TLS
    ssl_certificate     /etc/ssl/cert.pem;
    ssl_certificate_key /etc/ssl/key.pem;
}

This snippet ensures that content is never served over unencrypted HTTP: the first block redirects (code 301) all port 80 traffic to HTTPS. The second configures TLS allowing only versions 1.2 and 1.3 (the older ones have known vulnerabilities).

An essential rule about passwords: they are never encrypted or stored in plaintext; slow, salted hash functions are applied (bcrypt, scrypt, Argon2). This way, even if the database is stolen, the original passwords cannot be recovered.

Common Mistakes and Tips

  • Trusting only the client. Validation and authorization in the browser or mobile app can be bypassed trivially. Everything is validated and authorized on the server.
  • Believing the JWT is encrypted. It is not. Do not put sensitive data in the payload; anyone can read it.
  • Inventing your own cryptography. It almost always ends badly. Use standard libraries and protocols, reviewed and maintained.
  • Storing passwords with MD5/SHA or in cleartext. Use hash functions designed for passwords (bcrypt, Argon2) with salt.
  • Error messages that are too detailed. Saying "the user does not exist" versus "incorrect password" helps the attacker. Respond generically.
  • Not updating dependencies. Outdated libraries (A06) are one of the most frequent attack vectors. Scan and update.
  • Tip: think like an attacker. At every data input ask yourself "what happens if this is malicious?".

Exercises

  1. AuthN vs. AuthZ. A user logs in correctly but, when trying to access the administration panel, receives a 403 (Forbidden) error. Did authentication or authorization fail? Justify it.

  2. Spot the vulnerability. Review this code and state which OWASP Top 10 risk it contains and how you would fix it:

    String query = "SELECT * FROM products WHERE name = '" + search + "'";
    statement.executeQuery(query);
    
  3. Encryption decision. A messaging application sends messages between users and stores them in its database. What types of encryption does it need and why each one?

Solutions

  1. Authorization (AuthZ) failed, not authentication. The 403 (Forbidden) error means the system knows who the user is (they authenticated fine, it is not a 401), but that user does not have permissions to access the administration panel. If authentication had failed, the code would be 401 (Unauthorized/not identified).

  2. It is SQL injection (A03): the search variable is concatenated directly into the query, allowing an attacker to manipulate the SQL. Fix with a parameterized query:

    PreparedStatement ps = conn.prepareStatement(
        "SELECT * FROM products WHERE name = ?");
    ps.setString(1, search);
    ps.executeQuery();
    

    The ? prevents the user's value from being interpreted as SQL code.

  3. It needs both. Encryption in transit (TLS/HTTPS) so no one can intercept the messages while they travel over the network between the device and the server. And encryption at rest to protect the messages stored in the database against theft of the disk or a backup. (The most demanding apps add end-to-end encryption, where not even the server itself can read them.)

Conclusion

You have learned that security is designed from the start through layers (defense in depth) and minimal permissions, that authenticating (who you are) is distinct from authorizing (what you can do), that OAuth2/OIDC/JWT are the standard pieces of modern identity, that the OWASP Top 10 catalogs the risks you must know, and that data is encrypted both in transit and at rest. Security is never a finished product: it is a continuous process of prevention, detection, and improvement.

And to detect both attacks and performance problems, we need to see what is happening inside the system. In the next lesson, Observability: Logging, Metrics, and Tracing, we will learn to instrument the application to understand its behavior in production.

Application Architecture Course

Module 1: Fundamentals of Application Architecture

Module 2: Design Principles and Tactics

Module 3: Architectural Styles and Patterns

Module 4: Distributed Architectures and Microservices

Module 5: Event-Driven Architectures and Messaging

Module 6: Domain-Driven Design (DDD)

Module 7: Data and Persistence

Module 8: Cloud Architecture and Deployment

Module 9: Quality, Security and Observability

Module 10: Evolution, Governance and Case Studies

© Copyright 2026. All rights reserved