Skip to content
Back to Blog
Security & Best Practices

Security Practices That Actually Protect Production Applications (Part 1)

Magdalena Furman
January 31, 2026
4 min read

Security isn't a feature you add at the end — it's a foundation you build from day one.

Yet most applications still get it wrong. Not because developers don't care, but because security advice is often either too abstract to act on or so framework-specific that it doesn't translate to real systems.

This article is written for backend engineers, full-stack developers, and technical leads building production applications. It assumes you already know how to build APIs and ship features — and focuses instead on the security practices that actually hold up under real traffic and real attackers.

These aren't theoretical best practices. They're patterns we've implemented and battle-tested across multiple production systems at The Better Software Initiative. No silver bullets, no compliance theater — just the fundamentals that meaningfully reduce risk when applied consistently.

TL;DR

• Short-lived access tokens + revocable refresh tokens
• Authorization enforced in the service layer
• Strict input validation and output escaping
• Proper encryption for secrets and credentials

A. Authentication & Authorization

If authentication and authorization are weak, no amount of encryption or infrastructure hardening will save you.

JWT-Based Authentication

Use access tokens (short-lived, 15-30 minutes) and refresh tokens (long-lived, 7-30 days). Access tokens authorize requests. Refresh tokens generate new access tokens.

Why this matters: If an access token is compromised, it expires quickly. If a refresh token is stolen, you can revoke it server-side.

Stateless Sessions

Avoid server-side session storage. JWTs are self-contained — the server validates the signature and extracts claims without database lookups. This scales horizontally and simplifies deployment.

Method-Level Security

Don't rely on controller-level checks. Use method-level annotations like @PreAuthorize to enforce authorization at the service layer. This prevents bypasses through internal method calls.

Role-Based Access Control (RBAC)

Define clear roles (e.g., ADMIN, USER, STORE_OWNER). Use flags like isSystemAdmin for privileged operations.

Ownership Validation

Critical: Users should only access their own data. Always validate ownership in your service layer:

if (!store.getOwnerId().equals(currentUser.getId())) {
  throw new ForbiddenException("Access denied");
}

B. Input Validation

Never trust user input. Ever. Validate everything at the API boundary.

Custom Validators

Standard validators like @NotBlank and @Size aren't enough. Implement custom validators:

  • @AsciiOnly — Restricts input to a known-safe character set to avoid encoding ambiguities and downstream injection issues
  • @StrongPassword — Enforces complexity (min length, uppercase, lowercase, numbers, special chars)
  • @DomainEmail — Restricts emails to allowed domains (e.g., corporate email only)

SQL Injection Prevention

Use ORM frameworks (JPA/Hibernate, TypeORM, SQLAlchemy) with parameterized queries. Never concatenate user input into SQL strings.

// ❌ NEVER DO THIS
String query = "SELECT * FROM users WHERE email = '" + userInput + "'";

// ✅ DO THIS
@Query("SELECT u FROM User u WHERE u.email = :email")
User findByEmail(@Param("email") String email);

XSS Prevention

Set security headers (see section D) and sanitize output. Modern frameworks (React, Vue, Angular) escape output by default, but be careful with dangerouslySetInnerHTML or v-html.

C. Encryption

Data at Rest

Encrypt sensitive data before storing it:

  • API keys/secrets: Use AES-256-GCM encryption. Store the encryption key in environment variables, not in code.
  • Passwords: Use BCrypt (or Argon2) with a cost factor of 10-12. Never store plaintext passwords.
// Example: JPA converter for encrypted fields
@Converter
public class EncryptedStringConverter implements AttributeConverter<String, String> {
  private final AesGcmEncryption encryption;

  @Override
  public String convertToDatabaseColumn(String attribute) {
    return encryption.encrypt(attribute);
  }

  @Override
  public String convertToEntityAttribute(String dbData) {
    return encryption.decrypt(dbData);
  }
}

Data in Transit

Always use HTTPS in production. Enforce it with HSTS headers. Use TLS/SSL for database connections.

The Bottom Line

Authentication, input validation, and encryption form the foundation of application security. Get these wrong and nothing else matters. Get them right and you've eliminated the majority of common vulnerabilities before an attacker ever reaches your infrastructure.

But protecting your data is only half the battle. In Part 2, we'll cover the infrastructure-level controls that prevent abuse at scale — security headers, rate limiting, CORS, and webhook verification — the layers that often get skipped until something goes wrong.

At The Better Software Initiative, we build security into every layer of the applications we develop. From authentication architecture to production hardening, we can help you build systems that are secure by design.

Let's talk.

Magdalena Furman

Magdalena Furman

Co-founder & Director @ TBSI

10+ years of experience as a Backend Engineer. Specialized in building secure, scalable systems with Java/Spring Boot, AWS, and Terraform. Expert in application security, authentication architecture, and production-grade system design.