Definition
- HTTPS – encrypts all data in transit with TLS; enforced with redirects and HSTS so credentials and tokens cannot be sniffed.
- JWT/OAuth2 – authentication proving identity via a signed token the client sends as a
Bearertoken; the server verifies signature, expiry, issuer, and audience. - RBAC/Scopes – authorization enforcing what an authenticated user may do, using roles (
ADMIN,CUSTOMER) or OAuth2 scopes (transfers:write). - Input validation –
@NotBlank,@DecimalMinon request DTOs plus parameterized queries to reject malformed and malicious data. - Rate limiting – caps requests per client per window; returns
429 Too Many RequestswithRetry-Afterheader. - Secret management – credentials from environment variables or Vault, never in code; audit logs mask passwords and tokens.
Think of protecting National Bank of India's vault. HTTPS is the armoured pipe money travels through. The JWT token is Rajesh's ID badge. Role-based access is the rule that only managers enter the vault room. Input validation is the guard checking every form for forged entries. Rate limiting is the turnstile stopping a thousand PIN guesses per minute. Secret management is keeping the master keys in a locked safe, never taped to the wall. Logging is the CCTV recording who did what, without filming anyone's PIN.
Understanding the Concepts
What is HTTPS and why is it mandatory?
HTTPS encrypts everything in transit using TLS. Without it, a token sniffed on public WiFi gives an attacker full access. You enforce HTTPS everywhere, redirect HTTP to HTTPS, and use HSTS so browsers refuse plaintext.
package com.bank.config;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.requiresChannel(c -> c.anyRequest().requiresSecure()); // force HTTPS
http.headers(h -> h.httpStrictTransportSecurity(
hsts -> hsts.maxAgeInSeconds(31536000).includeSubDomains(true)));
return http.build();
}
}
How does JWT/OAuth2 authentication work?
A JWT is a signed token the client sends in the Authorization header. The server verifies the signature, expiry, issuer, and audience without a database lookup, which fits REST statelessness.
package com.bank.security;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import java.security.PublicKey;
import java.util.List;
public class JwtValidator {
private final PublicKey publicKey;
public JwtValidator(PublicKey publicKey) {
this.publicKey = publicKey;
}
public Claims validate(String bearerToken) {
String token = bearerToken.replace("Bearer ", "");
Jws<Claims> jws = Jwts.parser()
.verifyWith(publicKey)
.build()
.parseSignedClaims(token);
return jws.getPayload();
// Contains: sub (user), roles, exp (expiry), iss (issuer), aud (audience)
}
}
How does RBAC/scope authorization work?
Role-based access control maps users to roles and restricts endpoints. OAuth2 scopes are finer, like transfers:write. In Spring Security you use @PreAuthorize.
package com.bank.controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1")
public class SecureController {
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/reports/fraud")
public FraudReport fraudReport() { return reportService.generate(); }
@PreAuthorize("hasAuthority('SCOPE_transfers:write')")
@PostMapping("/transfers")
public TransferResponse transfer(@Valid @RequestBody TransferRequest req) {
return transferService.execute(req);
}
// Missing role → 403 Forbidden
}
How does input validation protect the API?
Bean validation annotations on request DTOs reject malformed data at the boundary. Parameterized queries prevent SQL injection.
package com.bank.dto;
import jakarta.validation.constraints.*;
import java.math.BigDecimal;
public record TransferRequest(
@NotBlank String fromAccount,
@NotBlank String toAccount,
@DecimalMin("0.01") @DecimalMax("10000000") BigDecimal amount
) {}
// Parameterized query – prevents SQL injection:
@Query("SELECT a FROM Account a WHERE a.id = :id")
Account findById(@Param("id") String id);
// Never: "SELECT * FROM accounts WHERE id = '" + id + "'";
How does rate limiting work?
Rate limiting caps requests per client per time window. When exceeded, return 429 Too Many Requests with Retry-After.
package com.bank.filter;
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
public class RateLimitFilter implements Filter {
private final ConcurrentHashMap<String, AtomicInteger> counters = new ConcurrentHashMap<>();
private static final int LIMIT = 100;
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String clientId = request.getHeader("X-Client-Id");
AtomicInteger count = counters.computeIfAbsent(clientId, k -> new AtomicInteger());
int current = count.incrementAndGet();
response.setHeader("X-RateLimit-Limit", String.valueOf(LIMIT));
response.setHeader("X-RateLimit-Remaining", String.valueOf(Math.max(0, LIMIT - current)));
if (current > LIMIT) {
response.setStatus(429);
response.setHeader("Retry-After", "60");
response.getWriter().write("{\"error\":\"Rate limit exceeded\"}");
return;
}
chain.doFilter(req, res);
}
}
How do you manage secrets?
Credentials, signing keys, and database passwords never live in code. They come from environment variables or a secrets manager like Vault.
// Wrong – hardcoded secret:
private static final String JWT_SECRET = "super-secret-key-123"; // breach waiting
// Correct – from environment:
@Value("${jwt.signing-key}")
private String signingKey; // injected from env or Vault
# application.yml – reference env variable:
jwt:
signing-key: ${JWT_SIGNING_KEY}
How should audit logging work?
Logs record security events: logins, permission denials, transfers, and admin actions. Crucially, logs must never contain passwords, tokens, or full card numbers.
package com.bank.audit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AuditLogger {
private static final Logger log = LoggerFactory.getLogger(AuditLogger.class);
public void logTransfer(String actor, String fromAccount, String toAccount, String outcome) {
log.info("AUDIT actor={} action=TRANSFER from={} to={} outcome={}",
actor, fromAccount, toAccount, outcome);
// Never log: password, token, full card number
}
}
What are common mistakes?
The most common mistake is relying on a single layer. HTTPS alone does not prevent a stolen token from being reused. JWT alone does not prevent injection. Rate limiting alone does not stop an authorized insider.
// Wrong – only HTTPS, no auth:
@GetMapping("/accounts/{id}")
public AccountResponse get(@PathVariable String id) {
return service.get(id); // anyone with HTTPS can access any account!
}
// Correct – HTTPS + auth + RBAC:
@PreAuthorize("hasRole('CUSTOMER') and #id == authentication.name")
@GetMapping("/accounts/{id}")
public AccountResponse get(@PathVariable String id) {
return service.get(id);
}
What should a senior engineer know?
These layers are complementary, not alternatives. HTTPS protects the wire but not a stolen token; short token lifetimes limit that damage. Validation stops injection but not an authorized user abusing their rights; least-privilege scopes limit that. The mindset is defence in depth and least privilege: assume any single control can fail, and ensure another catches it. In a bank, that layered redundancy is the difference between an incident and a catastrophe.
Visual Overview
Defence-in-Depth Layers:
Client ─── HTTPS (TLS) ───→ API Gateway
│
Rate Limiting (429)
│
JWT Validation
(signature, expiry)
│
RBAC / Scopes (403)
│
Input Validation
(@NotBlank, parameterized queries)
│
Service Logic
│
Audit Logging
(who, what, when, outcome)
│
Secret Management
(Vault / env vars)
Key Takeaway
Securing a REST API is defence in depth across seven complementary layers. HTTPS encrypts data in transit so tokens and account data cannot be sniffed. JWT/OAuth2 authentication proves identity with a signed token carrying claims. RBAC and scopes enforce permissions so an authenticated user only does what their role allows. Input validation rejects malformed data at the boundary with bean validation on request DTOs and parameterized queries. Rate limiting caps requests per client and returns 429 with Retry-After. Secret management keeps signing keys and database passwords out of code. Audit logging records who did what without ever writing passwords or tokens.
At National Bank of India, each layer earned its place. A token was sniffed on a cafe network because a test endpoint leaked over plain HTTP. A customer guessed the admin fraud report URL because there was no authorization check. A script tried thousands of login attempts before rate limiting was added. And a token was once found written to a log file.
The mindset is defence in depth: HTTPS protects the wire but not a stolen token, so short token lifetimes limit that damage. Validation stops injection but not an authorized user abusing their rights, so least-privilege scopes limit that. Each layer compensates for the others' blind spots.
Remember one sentence: secure the API in seven layers, because no single control is enough and each one covers a gap the others miss.
---